Escape GET variables centrally
Escape GET variables centrally

file:a/about.php -> file:b/about.php
--- a/about.php
+++ b/about.php
@@ -12,7 +12,7 @@
 href="https://github.com/maxious/ACTBus-data">transit 
 feed</a> and <a href="https://github.com/maxious/ACTBus-ui">this 
 site</a> available from github.<br />
-Uses jQuery Mobile, PHP, Ruby, Python, Google Transit Feed Specification tools, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder and Tile Service<br />
+Uses jQuery Mobile, PHP, PostgreSQL, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder and Tile Service<br />
 <br />
 Feedback encouraged; contact maxious@lambdacomplex.org<br />
     <br />
@@ -25,7 +25,7 @@
 All offers are not binding and without obligation. The Author expressly reserves the right, in his discretion, to suspend, 
 change, modify, add or remove portions of the Site and to restrict or terminate the use and accessibility of the Site 
 without prior notice. </small>
-<?
+<?php
 include_footer();
 ?>
 

--- a/aws/awsStartup.sh
+++ b/aws/awsStartup.sh
@@ -1,7 +1,8 @@
 #!/bin/bash
-#this script should be run from a fresh git checkout from http://maxious.lambdacomplex.org
+#this script should be run from a fresh git checkout from github
 #ami base must have yum install lighttpd-fastcgi, git, tomcat6 
-#screen php-cli php-gd tomcat6-webapps tomcat6-admin-webapps svn maven2
+#php-cli php-gd tomcat6-webapps tomcat6-admin-webapps svn maven2
+#postgres postgres-server php-pg
 #http://www.how2forge.org/installing-lighttpd-with-php5-and-mysql-support-on-fedora-12
 
 cp /root/aws.php /tmp/
@@ -10,12 +11,22 @@
 chcon -R -h root:object_r:httpd_sys_content_t /var/www/*
 chcon -R -t httpd_sys_content_rw_t /var/www/lib/staticmaplite/cache
 chmod -R 777 /var/www/lib/staticmaplite/cache 
+chcon -R -t httpd_sys_content_rw_t /var/www/labs/tiles
+chmod -R 777 /var/www/labs/tiles
 wget http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip \
 -O /var/www/cbrfeed.zip
-easy_install transitfeed
-easy_install simplejson
-screen -S viewsh -X quit
-screen -S viewsh -d -m /var/www/view.sh
+
+createdb transitdata
+createlang -d transitdata plpgsql
+psql -d transitdata -f /var/www/lib/postgis.sql
+# curl https://github.com/maxious/ACTBus-ui/raw/master/transitdata.cbrfeed.sql.gz -o transitdata.cbrfeed.sql.gz 
+#made with pg_dump transitdata | gzip -c >  transitdata.cbrfeed.sql.gz
+gunzip /var/www/transitdata.cbrfeed.sql.gz
+psql -d transitdata -f /var/www/transitdata.cbrfeed.sql
+#createuser transitdata -SDRP
+#password transitdata
+#psql -d transitdata -c \"GRANT SELECT ON TABLE agency,calendar,calendar_dates,routes,stop_times,stops,trips TO transitdata;\"
+php /var/www/updatedb.php
 
 wget http://s3-ap-southeast-1.amazonaws.com/busresources/Graph.obj \
 -O /tmp/Graph.obj
@@ -26,4 +37,3 @@
 -O /usr/share/tomcat6/webapps/opentripplanner-api-webapp.war
 /etc/init.d/tomcat6 restart
 
-

file:b/aws/pg_hba.conf (new)
--- /dev/null
+++ b/aws/pg_hba.conf
@@ -1,1 +1,77 @@
+# PostgreSQL Client Authentication Configuration File
+# ===================================================
+#
+# Refer to the "Client Authentication" section in the
+# PostgreSQL documentation for a complete description
+# of this file.  A short synopsis follows.
+#
+# This file controls: which hosts are allowed to connect, how clients
+# are authenticated, which PostgreSQL user names they can use, which
+# databases they can access.  Records take one of these forms:
+#
+# local      DATABASE  USER  METHOD  [OPTIONS]
+# host       DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
+# hostssl    DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
+# hostnossl  DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
+#
+# (The uppercase items must be replaced by actual values.)
+#
+# The first field is the connection type: "local" is a Unix-domain socket,
+# "host" is either a plain or SSL-encrypted TCP/IP socket, "hostssl" is an
+# SSL-encrypted TCP/IP socket, and "hostnossl" is a plain TCP/IP socket.
+#
+# DATABASE can be "all", "sameuser", "samerole", a database name, or
+# a comma-separated list thereof.
+#
+# USER can be "all", a user name, a group name prefixed with "+", or
+# a comma-separated list thereof.  In both the DATABASE and USER fields
+# you can also write a file name prefixed with "@" to include names from
+# a separate file.
+#
+# CIDR-ADDRESS specifies the set of hosts the record matches.
+# It is made up of an IP address and a CIDR mask that is an integer
+# (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that specifies
+# the number of significant bits in the mask.  Alternatively, you can write
+# an IP address and netmask in separate columns to specify the set of hosts.
+#
+# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
+# "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
+# in clear text; "md5" is preferred since it sends encrypted passwords.
+#
+# OPTIONS are a set of options for the authentication in the format
+# NAME=VALUE. The available options depend on the different authentication
+# methods - refer to the "Client Authentication" section in the documentation
+# for a list of which options are available for which authentication methods.
+#
+# Database and user names containing spaces, commas, quotes and other special
+# characters must be quoted. Quoting one of the keywords "all", "sameuser" or
+# "samerole" makes the name lose its special character, and just match a
+# database or username with that name.
+#
+# This file is read on server startup and when the postmaster receives
+# a SIGHUP signal.  If you edit the file on a running system, you have
+# to SIGHUP the postmaster for the changes to take effect.  You can use
+# "pg_ctl reload" to do that.
 
+# Put your actual configuration here
+# ----------------------------------
+#
+# If you want to allow non-local connections, you need to add more
+# "host" records. In that case you will also need to make PostgreSQL listen
+# on a non-local interface via the listen_addresses configuration parameter,
+# or via the -i or -h command line switches.
+#
+
+
+
+# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD
+
+# "local" is for Unix domain socket connections only
+local   all         all                               trust
+# IPv4 local connections:
+host    all         all         127.0.0.1/32          trust
+# IPv6 local connections:
+host    all         all         ::1/128               trust
+#Allow any IP to connect, with a password:
+host    all         all         0.0.0.0          0.0.0.0      md5
+

--- /dev/null
+++ b/aws/postgresql.conf
@@ -1,1 +1,502 @@
-
+# -----------------------------
+# PostgreSQL configuration file
+# -----------------------------
+#
+# This file consists of lines of the form:
+#
+#   name = value
+#
+# (The "=" is optional.)  Whitespace may be used.  Comments are introduced with
+# "#" anywhere on a line.  The complete list of parameter names and allowed
+# values can be found in the PostgreSQL documentation.
+#
+# The commented-out settings shown in this file represent the default values.
+# Re-commenting a setting is NOT sufficient to revert it to the default value;
+# you need to reload the server.
+#
+# This file is read on server startup and when the server receives a SIGHUP
+# signal.  If you edit the file on a running system, you have to SIGHUP the
+# server for the changes to take effect, or use "pg_ctl reload".  Some
+# parameters, which are marked below, require a server shutdown and restart to
+# take effect.
+#
+# Any parameter can also be given as a command-line option to the server, e.g.,
+# "postgres -c log_connections=on".  Some parameters can be changed at run time
+# with the "SET" SQL command.
+#
+# Memory units:  kB = kilobytes        Time units:  ms  = milliseconds
+#                MB = megabytes                     s   = seconds
+#                GB = gigabytes                     min = minutes
+#                                                   h   = hours
+#                                                   d   = days
+
+
+#------------------------------------------------------------------------------
+# FILE LOCATIONS
+#------------------------------------------------------------------------------
+
+# The default values of these variables are driven from the -D command-line
+# option or PGDATA environment variable, represented here as ConfigDir.
+
+#data_directory = 'ConfigDir'		# use data in another directory
+					# (change requires restart)
+#hba_file = 'ConfigDir/pg_hba.conf'	# host-based authentication file
+					# (change requires restart)
+#ident_file = 'ConfigDir/pg_ident.conf'	# ident configuration file
+					# (change requires restart)
+
+# If external_pid_file is not explicitly set, no extra PID file is written.
+#external_pid_file = '(none)'		# write an extra PID file
+					# (change requires restart)
+
+
+#------------------------------------------------------------------------------
+# CONNECTIONS AND AUTHENTICATION
+#------------------------------------------------------------------------------
+
+# - Connection Settings -
+
+listen_addresses = '*'		# what IP address(es) to listen on;
+					# comma-separated list of addresses;
+					# defaults to 'localhost', '*' = all
+					# (change requires restart)
+#port = 5432				# (change requires restart)
+max_connections = 100			# (change requires restart)
+# Note:  Increasing max_connections costs ~400 bytes of shared memory per 
+# connection slot, plus lock space (see max_locks_per_transaction).
+#superuser_reserved_connections = 3	# (change requires restart)
+#unix_socket_directory = ''		# (change requires restart)
+#unix_socket_group = ''			# (change requires restart)
+#unix_socket_permissions = 0777		# begin with 0 to use octal notation
+					# (change requires restart)
+#bonjour_name = ''			# defaults to the computer name
+					# (change requires restart)
+
+# - Security and Authentication -
+
+#authentication_timeout = 1min		# 1s-600s
+#ssl = off				# (change requires restart)
+#ssl_ciphers = 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH'	# allowed SSL ciphers
+					# (change requires restart)
+#ssl_renegotiation_limit = 512MB	# amount of data between renegotiations
+#password_encryption = on
+#db_user_namespace = off
+
+# Kerberos and GSSAPI
+#krb_server_keyfile = ''
+#krb_srvname = 'postgres'		# (Kerberos only)
+#krb_caseins_users = off
+
+# - TCP Keepalives -
+# see "man 7 tcp" for details
+
+#tcp_keepalives_idle = 0		# TCP_KEEPIDLE, in seconds;
+					# 0 selects the system default
+#tcp_keepalives_interval = 0		# TCP_KEEPINTVL, in seconds;
+					# 0 selects the system default
+#tcp_keepalives_count = 0		# TCP_KEEPCNT;
+					# 0 selects the system default
+
+
+#------------------------------------------------------------------------------
+# RESOURCE USAGE (except WAL)
+#------------------------------------------------------------------------------
+
+# - Memory -
+
+shared_buffers = 32MB			# min 128kB
+					# (change requires restart)
+#temp_buffers = 8MB			# min 800kB
+#max_prepared_transactions = 0		# zero disables the feature
+					# (change requires restart)
+# Note:  Increasing max_prepared_transactions costs ~600 bytes of shared memory
+# per transaction slot, plus lock space (see max_locks_per_transaction).
+# It is not advisable to set max_prepared_transactions nonzero unless you
+# actively intend to use prepared transactions.
+#work_mem = 1MB				# min 64kB
+#maintenance_work_mem = 16MB		# min 1MB
+#max_stack_depth = 2MB			# min 100kB
+
+# - Kernel Resource Usage -
+
+#max_files_per_process = 1000		# min 25
+					# (change requires restart)
+#shared_preload_libraries = ''		# (change requires restart)
+
+# - Cost-Based Vacuum Delay -
+
+#vacuum_cost_delay = 0ms		# 0-100 milliseconds
+#vacuum_cost_page_hit = 1		# 0-10000 credits
+#vacuum_cost_page_miss = 10		# 0-10000 credits
+#vacuum_cost_page_dirty = 20		# 0-10000 credits
+#vacuum_cost_limit = 200		# 1-10000 credits
+
+# - Background Writer -
+
+#bgwriter_delay = 200ms			# 10-10000ms between rounds
+#bgwriter_lru_maxpages = 100		# 0-1000 max buffers written/round
+#bgwriter_lru_multiplier = 2.0		# 0-10.0 multipler on buffers scanned/round
+
+# - Asynchronous Behavior -
+
+#effective_io_concurrency = 1		# 1-1000. 0 disables prefetching
+
+
+#------------------------------------------------------------------------------
+# WRITE AHEAD LOG
+#------------------------------------------------------------------------------
+
+# - Settings -
+
+#fsync = on				# turns forced synchronization on or off
+#synchronous_commit = on		# immediate fsync at commit
+#wal_sync_method = fsync		# the default is the first option 
+					# supported by the operating system:
+					#   open_datasync
+					#   fdatasync
+					#   fsync
+					#   fsync_writethrough
+					#   open_sync
+#full_page_writes = on			# recover from partial page writes
+#wal_buffers = 64kB			# min 32kB
+					# (change requires restart)
+#wal_writer_delay = 200ms		# 1-10000 milliseconds
+
+#commit_delay = 0			# range 0-100000, in microseconds
+#commit_siblings = 5			# range 1-1000
+
+# - Checkpoints -
+
+#checkpoint_segments = 3		# in logfile segments, min 1, 16MB each
+#checkpoint_timeout = 5min		# range 30s-1h
+#checkpoint_completion_target = 0.5	# checkpoint target duration, 0.0 - 1.0
+#checkpoint_warning = 30s		# 0 disables
+
+# - Archiving -
+
+#archive_mode = off		# allows archiving to be done
+				# (change requires restart)
+#archive_command = ''		# command to use to archive a logfile segment
+#archive_timeout = 0		# force a logfile segment switch after this
+				# number of seconds; 0 disables
+
+
+#------------------------------------------------------------------------------
+# QUERY TUNING
+#------------------------------------------------------------------------------
+
+# - Planner Method Configuration -
+
+#enable_bitmapscan = on
+#enable_hashagg = on
+#enable_hashjoin = on
+#enable_indexscan = on
+#enable_mergejoin = on
+#enable_nestloop = on
+#enable_seqscan = on
+#enable_sort = on
+#enable_tidscan = on
+
+# - Planner Cost Constants -
+
+#seq_page_cost = 1.0			# measured on an arbitrary scale
+#random_page_cost = 4.0			# same scale as above
+#cpu_tuple_cost = 0.01			# same scale as above
+#cpu_index_tuple_cost = 0.005		# same scale as above
+#cpu_operator_cost = 0.0025		# same scale as above
+#effective_cache_size = 128MB
+
+# - Genetic Query Optimizer -
+
+#geqo = on
+#geqo_threshold = 12
+#geqo_effort = 5			# range 1-10
+#geqo_pool_size = 0			# selects default based on effort
+#geqo_generations = 0			# selects default based on effort
+#geqo_selection_bias = 2.0		# range 1.5-2.0
+
+# - Other Planner Options -
+
+#default_statistics_target = 100	# range 1-10000
+#constraint_exclusion = partition	# on, off, or partition
+#cursor_tuple_fraction = 0.1		# range 0.0-1.0
+#from_collapse_limit = 8
+#join_collapse_limit = 8		# 1 disables collapsing of explicit 
+					# JOIN clauses
+
+
+#------------------------------------------------------------------------------
+# ERROR REPORTING AND LOGGING
+#------------------------------------------------------------------------------
+
+# - Where to Log -
+
+#log_destination = 'stderr'		# Valid values are combinations of
+					# stderr, csvlog, syslog and eventlog,
+					# depending on platform.  csvlog
+					# requires logging_collector to be on.
+
+# This is used when logging to stderr:
+logging_collector = on			# Enable capturing of stderr and csvlog
+					# into log files. Required to be on for
+					# csvlogs.
+					# (change requires restart)
+
+# These are only used if logging_collector is on:
+log_directory = 'pg_log'		# directory where log files are written,
+					# can be absolute or relative to PGDATA
+log_filename = 'postgresql-%a.log'	# log file name pattern,
+					# can include strftime() escapes
+log_truncate_on_rotation = on		# If on, an existing log file of the
+					# same name as the new log file will be
+					# truncated rather than appended to.
+					# But such truncation only occurs on
+					# time-driven rotation, not on restarts
+					# or size-driven rotation.  Default is
+					# off, meaning append to existing files
+					# in all cases.
+log_rotation_age = 1d			# Automatic rotation of logfiles will
+					# happen after that time.  0 disables.
+log_rotation_size = 0			# Automatic rotation of logfiles will 
+					# happen after that much log output.
+					# 0 disables.
+
+# These are relevant when logging to syslog:
+#syslog_facility = 'LOCAL0'
+#syslog_ident = 'postgres'
+
+#silent_mode = off			# Run server silently.
+					# DO NOT USE without syslog or
+					# logging_collector
+					# (change requires restart)
+
+
+# - When to Log -
+
+#client_min_messages = notice		# values in order of decreasing detail:
+					#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+					#   log
+					#   notice
+					#   warning
+					#   error
+
+#log_min_messages = warning		# values in order of decreasing detail:
+					#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+					#   info
+					#   notice
+					#   warning
+					#   error
+					#   log
+					#   fatal
+					#   panic
+
+#log_error_verbosity = default		# terse, default, or verbose messages
+
+#log_min_error_statement = error	# values in order of decreasing detail:
+				 	#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+				 	#   info
+					#   notice
+					#   warning
+					#   error
+					#   log
+					#   fatal
+					#   panic (effectively off)
+
+#log_min_duration_statement = -1	# -1 is disabled, 0 logs all statements
+					# and their durations, > 0 logs only
+					# statements running at least this number
+					# of milliseconds
+
+
+# - What to Log -
+
+#debug_print_parse = off
+#debug_print_rewritten = off
+#debug_print_plan = off
+#debug_pretty_print = on
+#log_checkpoints = off
+#log_connections = off
+#log_disconnections = off
+#log_duration = off
+#log_hostname = off
+#log_line_prefix = ''			# special values:
+					#   %u = user name
+					#   %d = database name
+					#   %r = remote host and port
+					#   %h = remote host
+					#   %p = process ID
+					#   %t = timestamp without milliseconds
+					#   %m = timestamp with milliseconds
+					#   %i = command tag
+					#   %c = session ID
+					#   %l = session line number
+					#   %s = session start timestamp
+					#   %v = virtual transaction ID
+					#   %x = transaction ID (0 if none)
+					#   %q = stop here in non-session
+					#        processes
+					#   %% = '%'
+					# e.g. '<%u%%%d> '
+#log_lock_waits = off			# log lock waits >= deadlock_timeout
+#log_statement = 'none'			# none, ddl, mod, all
+#log_temp_files = -1			# log temporary files equal or larger
+					# than the specified size in kilobytes;
+					# -1 disables, 0 logs all temp files
+#log_timezone = unknown			# actually, defaults to TZ environment
+					# setting
+
+
+#------------------------------------------------------------------------------
+# RUNTIME STATISTICS
+#------------------------------------------------------------------------------
+
+# - Query/Index Statistics Collector -
+
+#track_activities = on
+#track_counts = on
+#track_functions = none			# none, pl, all
+#track_activity_query_size = 1024
+#update_process_title = on
+#stats_temp_directory = 'pg_stat_tmp'
+
+
+# - Statistics Monitoring -
+
+#log_parser_stats = off
+#log_planner_stats = off
+#log_executor_stats = off
+#log_statement_stats = off
+
+
+#------------------------------------------------------------------------------
+# AUTOVACUUM PARAMETERS
+#------------------------------------------------------------------------------
+
+#autovacuum = on			# Enable autovacuum subprocess?  'on' 
+					# requires track_counts to also be on.
+#log_autovacuum_min_duration = -1	# -1 disables, 0 logs all actions and
+					# their durations, > 0 logs only
+					# actions running at least this number
+					# of milliseconds.
+#autovacuum_max_workers = 3		# max number of autovacuum subprocesses
+#autovacuum_naptime = 1min		# time between autovacuum runs
+#autovacuum_vacuum_threshold = 50	# min number of row updates before
+					# vacuum
+#autovacuum_analyze_threshold = 50	# min number of row updates before 
+					# analyze
+#autovacuum_vacuum_scale_factor = 0.2	# fraction of table size before vacuum
+#autovacuum_analyze_scale_factor = 0.1	# fraction of table size before analyze
+#autovacuum_freeze_max_age = 200000000	# maximum XID age before forced vacuum
+					# (change requires restart)
+#autovacuum_vacuum_cost_delay = 20ms	# default vacuum cost delay for
+					# autovacuum, in milliseconds;
+					# -1 means use vacuum_cost_delay
+#autovacuum_vacuum_cost_limit = -1	# default vacuum cost limit for
+					# autovacuum, -1 means use
+					# vacuum_cost_limit
+
+
+#------------------------------------------------------------------------------
+# CLIENT CONNECTION DEFAULTS
+#------------------------------------------------------------------------------
+
+# - Statement Behavior -
+
+#search_path = '"$user",public'		# schema names
+#default_tablespace = ''		# a tablespace name, '' uses the default
+#temp_tablespaces = ''			# a list of tablespace names, '' uses
+					# only default tablespace
+#check_function_bodies = on
+#default_transaction_isolation = 'read committed'
+#default_transaction_read_only = off
+#session_replication_role = 'origin'
+#statement_timeout = 0			# in milliseconds, 0 is disabled
+#vacuum_freeze_min_age = 50000000
+#vacuum_freeze_table_age = 150000000
+#xmlbinary = 'base64'
+#xmloption = 'content'
+
+# - Locale and Formatting -
+
+datestyle = 'iso, mdy'
+#intervalstyle = 'postgres'
+#timezone = unknown			# actually, defaults to TZ environment
+					# setting
+#timezone_abbreviations = 'Default'     # Select the set of available time zone
+					# abbreviations.  Currently, there are
+					#   Default
+					#   Australia
+					#   India
+					# You can create your own file in
+					# share/timezonesets/.
+#extra_float_digits = 0			# min -15, max 2
+#client_encoding = sql_ascii		# actually, defaults to database
+					# encoding
+
+# These settings are initialized by initdb, but they can be changed.
+lc_messages = 'en_US.UTF-8'			# locale for system error message
+					# strings
+lc_monetary = 'en_US.UTF-8'			# locale for monetary formatting
+lc_numeric = 'en_US.UTF-8'			# locale for number formatting
+lc_time = 'en_US.UTF-8'				# locale for time formatting
+
+# default configuration for text search
+default_text_search_config = 'pg_catalog.english'
+
+# - Other Defaults -
+
+#dynamic_library_path = '$libdir'
+#local_preload_libraries = ''
+
+
+#------------------------------------------------------------------------------
+# LOCK MANAGEMENT
+#------------------------------------------------------------------------------
+
+#deadlock_timeout = 1s
+#max_locks_per_transaction = 64		# min 10
+					# (change requires restart)
+# Note:  Each lock table slot uses ~270 bytes of shared memory, and there are
+# max_locks_per_transaction * (max_connections + max_prepared_transactions)
+# lock table slots.
+
+
+#------------------------------------------------------------------------------
+# VERSION/PLATFORM COMPATIBILITY
+#------------------------------------------------------------------------------
+
+# - Previous PostgreSQL Versions -
+
+#add_missing_from = off
+#array_nulls = on
+#backslash_quote = safe_encoding	# on, off, or safe_encoding
+#default_with_oids = off
+#escape_string_warning = on
+#regex_flavor = advanced		# advanced, extended, or basic
+#sql_inheritance = on
+#standard_conforming_strings = off
+#synchronize_seqscans = on
+
+# - Other Platforms and Clients -
+
+#transform_null_equals = off
+
+
+#------------------------------------------------------------------------------
+# CUSTOMIZED OPTIONS
+#------------------------------------------------------------------------------
+
+#custom_variable_classes = ''		# list of custom variable class names
+

 Binary files a/css/images/01-refresh.png and /dev/null differ
file:a/css/images/02-redo.png (deleted)
 Binary files a/css/images/02-redo.png and /dev/null differ
 Binary files a/css/images/06-magnify.png and /dev/null differ
 Binary files a/css/images/07-map-marker.png and /dev/null differ
 Binary files a/css/images/101-gameplan.png and /dev/null differ
file:a/css/images/102-walk.png (deleted)
 Binary files a/css/images/102-walk.png and /dev/null differ
file:a/css/images/103-map.png (deleted)
 Binary files a/css/images/103-map.png and /dev/null differ
 Binary files a/css/images/121-landscape.png and /dev/null differ
 Binary files a/css/images/13-target.png and /dev/null differ
 Binary files a/css/images/139-flags.png and /dev/null differ
 Binary files a/css/images/145-persondot.png and /dev/null differ
 Binary files a/css/images/184-warning.png and /dev/null differ
 Binary files a/css/images/193-location-arrow.png and /dev/null differ
file:a/css/images/28-star.png (deleted)
 Binary files a/css/images/28-star.png and /dev/null differ
file:a/css/images/53-house.png (deleted)
 Binary files a/css/images/53-house.png and /dev/null differ
 Binary files a/css/images/55-network.png and /dev/null differ
 Binary files a/css/images/57-download.png and /dev/null differ
 Binary files a/css/images/58-bookmark.png and /dev/null differ
file:a/css/images/59-flag.png (deleted)
 Binary files a/css/images/59-flag.png and /dev/null differ
 Binary files a/css/images/60-signpost.png and /dev/null differ
file:a/css/images/73-radar.png (deleted)
 Binary files a/css/images/73-radar.png and /dev/null differ
 Binary files a/css/images/74-location.png and /dev/null differ
 Binary files a/css/images/83-calendar.png and /dev/null differ
 Binary files /dev/null and b/css/images/91-beaker-2.png differ
--- a/css/jquery-mobile-1.0a3.css
+++ /dev/null
@@ -1,16 +1,1 @@
-/*!
- * jQuery Mobile v1.0a3
- * http://jquerymobile.com/
- *
- * Copyright 2010, jQuery Project
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- */
-/*!
- * jQuery Mobile v1.0a3
- * http://jquerymobile.com/
- *
- * Copyright 2010, jQuery Project
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- */.ui-bar-a{border:1px solid #2a2a2a;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-moz-linear-gradient(top,#3c3c3c,#111);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3c3c3c),color-stop(1,#111));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#3c3c3c', EndColorStr='#111111')"}.ui-bar-a,.ui-bar-a input,.ui-bar-a select,.ui-bar-a textarea,.ui-bar-a button{font-family:Helvetica,Arial,sans-serif}.ui-bar-a .ui-link-inherit{color:#fff}.ui-bar-a .ui-link{color:#7cc4e7;font-weight:bold}.ui-body-a{border:1px solid #2a2a2a;background:#222;color:#fff;text-shadow:0 1px 0 #000;font-weight:normal;background-image:-moz-linear-gradient(top,#666,#222);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(1,#222));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#666666', EndColorStr='#222222)')"}.ui-body-a,.ui-body-a input,.ui-body-a select,.ui-body-a textarea,.ui-body-a button{font-family:Helvetica,Arial,sans-serif}.ui-body-a .ui-link-inherit{color:#fff}.ui-body-a .ui-link{color:#2489ce;font-weight:bold}.ui-br{border-bottom:1px solid rgba(130,130,130,.3)}.ui-btn-up-a{border:1px solid #222;background:#333;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 -1px 1px #000;text-decoration:none;background-image:-moz-linear-gradient(top,#555,#333);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#555),color-stop(1,#333));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#555555', EndColorStr='#333333')"}.ui-btn-up-a a.ui-link-inherit{color:#fff}.ui-btn-hover-a{border:1px solid #000;background:#444;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #000;text-decoration:none;background-image:-moz-linear-gradient(top,#666,#444);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(1,#444));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#666666', EndColorStr='#444444')"}.ui-btn-hover-a a.ui-link-inherit{color:#fff}.ui-btn-down-a{border:1px solid #000;background:#3d3d3d;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #000;background-image:-moz-linear-gradient(top,#333,#5a5a5a);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#333),color-stop(1,#5a5a5a));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#333333', EndColorStr='#5a5a5a')"}.ui-btn-down-a a.ui-link-inherit{color:#fff}.ui-btn-up-a,.ui-btn-hover-a,.ui-btn-down-a{font-family:Helvetica,Arial,sans-serif}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #254f7a;background-image:-moz-linear-gradient(top,#81a8ce,#5e87b0);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#81a8ce),color-stop(1,#5e87b0));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#81a8ce', EndColorStr='#5e87b0')"}.ui-bar-b,.ui-bar-b input,.ui-bar-b select,.ui-bar-b textarea,.ui-bar-b button{font-family:Helvetica,Arial,sans-serif}.ui-bar-b .ui-link-inherit{color:#fff}.ui-bar-b .ui-link{color:#7cc4e7;font-weight:bold}.ui-body-b{border:1px solid #c6c6c6;background:#ccc;color:#333;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-moz-linear-gradient(top,#e6e6e6,#ccc);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e6e6e6),color-stop(1,#ccc));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#e6e6e6', EndColorStr='#cccccc')"}.ui-body-b,.ui-body-b input,.ui-body-b select,.ui-body-b textarea,.ui-body-b button{font-family:Helvetica,Arial,sans-serif}.ui-body-b .ui-link-inherit{color:#333}.ui-body-b .ui-link{color:#2489ce;font-weight:bold}.ui-btn-up-b{border:1px solid #145072;background:#2567ab;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 -1px 1px #145072;text-decoration:none;background-image:-moz-linear-gradient(top,#4e89c5,#2567ab);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#5f9cc5),color-stop(1,#396b9e));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#4e89c5', EndColorStr='#2567ab')"}.ui-btn-up-b a.ui-link-inherit{color:#fff}.ui-btn-hover-b{border:1px solid #00516e;background:#4b88b6;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #014d68;background-image:-moz-linear-gradient(top,#72b0d4,#4b88b6);text-decoration:none;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#72b0d4),color-stop(1,#4b88b6));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#72b0d4', EndColorStr='#4b88b6')"}.ui-btn-hover-b a.ui-link-inherit{color:#fff}.ui-btn-down-b{border:1px solid #225377;background:#4e89c5;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #225377;background-image:-moz-linear-gradient(top,#396b9e,#4e89c5);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#396b9e),color-stop(1,#4e89c5));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#396b9e', EndColorStr='#4e89c5')"}.ui-btn-down-b a.ui-link-inherit{color:#fff}.ui-btn-up-b,.ui-btn-hover-b,.ui-btn-down-b{font-family:Helvetica,Arial,sans-serif}.ui-bar-c{border:1px solid #b3b3b3;background:#e9eaeb;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#f0f0f0,#e9eaeb);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f0f0f0),color-stop(1,#e9eaeb));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#f0f0f0', EndColorStr='#e9eaeb')"}.ui-bar-c,.ui-bar-c input,.ui-bar-c select,.ui-bar-c textarea,.ui-bar-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c{border:1px solid #b3b3b3;color:#333;text-shadow:0 1px 0 #fff;background:#f0f0f0;background-image:-moz-linear-gradient(top,#eee,#ddd);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eee),color-stop(1,#ddd));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#dddddd')"}.ui-body-c,.ui-body-c input,.ui-body-c select,.ui-body-c textarea,.ui-body-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c .ui-link-inherit{color:#333}.ui-body-c .ui-link{color:#2489ce;font-weight:bold}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#444;cursor:pointer;text-shadow:0 1px 1px #f6f6f6;text-decoration:none;background-image:-moz-linear-gradient(top,#fefefe,#eee);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fdfdfd),color-stop(1,#eee));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fdfdfd', EndColorStr='#eeeeee')"}.ui-btn-up-c a.ui-link-inherit{color:#2f3e46}.ui-btn-hover-c{border:1px solid #bbb;background:#dadada;font-weight:bold;color:#101010;text-decoration:none;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#ededed,#dadada);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ededed),color-stop(1,#dadada));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#ededed', EndColorStr='#dadada')"}.ui-btn-hover-c a.ui-link-inherit{color:#2f3e46}.ui-btn-down-c{border:1px solid #808080;background:#fdfdfd;font-weight:bold;color:#111;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#eee,#fdfdfd);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eee),color-stop(1,#fdfdfd));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#fdfdfd')"}.ui-btn-down-c a.ui-link-inherit{color:#2f3e46}.ui-btn-up-c,.ui-btn-hover-c,.ui-btn-down-c{font-family:Helvetica,Arial,sans-serif}.ui-bar-d{border:1px solid #ccc;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-moz-linear-gradient(top,#ddd,#bbb);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ddd),color-stop(1,#bbb));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#dddddd', EndColorStr='#bbbbbb')"}.ui-bar-d,.ui-bar-d input,.ui-bar-d select,.ui-bar-d textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-d .ui-link-inherit{color:#333}.ui-bar-d .ui-link{color:#2489ce;font-weight:bold}.ui-body-d{border:1px solid #ccc;color:#333;text-shadow:0 1px 0 #fff;background:#fff}.ui-body-d,.ui-body-d input,.ui-body-d select,.ui-body-d textarea,.ui-body-d button{font-family:Helvetica,Arial,sans-serif}.ui-body-d .ui-link-inherit{color:#333}.ui-body-d .ui-link{color:#2489ce;font-weight:bold}.ui-btn-up-d{border:1px solid #ccc;background:#fff;font-weight:bold;color:#444;text-decoration:none;text-shadow:0 1px 1px #fff}.ui-btn-up-d a.ui-link-inherit{color:#333}.ui-btn-hover-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#222;cursor:pointer;text-shadow:0 1px 1px #fff;text-decoration:none;background-image:-moz-linear-gradient(top,#fdfdfd,#eee);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fdfdfd),color-stop(1,#eee));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fdfdfd', EndColorStr='#eeeeee')"}.ui-btn-hover-d a.ui-link-inherit{color:#222}.ui-btn-down-d{border:1px solid #aaa;background:#fff;font-weight:bold;color:#111;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#eee,#fff);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eee),color-stop(1,#fff));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#ffffff')"}.ui-btn-down-d a.ui-link-inherit{border:1px solid #808080;background:#ced0d2;font-weight:bold;color:#111;text-shadow:none;background-image:-moz-linear-gradient(top,#ccc,#eee);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ccc),color-stop(1,#eee));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#cccccc', EndColorStr='#eeeeee')"}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-moz-linear-gradient(top,#fceda7,#fadb4e);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fceda7),color-stop(1,#fadb4e));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fceda7', EndColorStr='#fadb4e')"}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-e .ui-link-inherit{color:#333}.ui-bar-e .ui-link{color:#2489ce;font-weight:bold}.ui-body-e{border:1px solid #f7c942;color:#333;text-shadow:0 1px 0 #fff;background:#faeb9e;background-image:-moz-linear-gradient(top,#fff,#faeb9e);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(1,#faeb9e));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#faeb9e')"}.ui-body-e,.ui-body-e input,.ui-body-e select,.ui-body-e textarea,.ui-body-e button{font-family:Helvetica,Arial,sans-serif}.ui-body-e .ui-link-inherit{color:#333}.ui-body-e .ui-link{color:#2489ce;font-weight:bold}.ui-btn-up-e{border:1px solid #f7c942;background:#fadb4e;font-weight:bold;color:#333;cursor:pointer;text-shadow:0 1px 1px #fe3;text-decoration:none;text-shadow:0 1px 0 #fff;background-image:-moz-linear-gradient(top,#fceda7,#fadb4e);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fceda7),color-stop(1,#fadb4e));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fceda7', EndColorStr='#fadb4e')"}.ui-btn-up-e a.ui-link-inherit{color:#333}.ui-btn-hover-e{border:1px solid #e79952;background:#fbe26f;font-weight:bold;color:#111;text-decoration:none;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#fcf0b5,#fbe26f);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fcf0b5),color-stop(1,#fbe26f));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fcf0b5', EndColorStr='#fbe26f')"}.ui-btn-hover-e a.ui-link-inherit{color:#333}.ui-btn-down-e{border:1px solid #f7c942;background:#fceda7;font-weight:bold;color:#111;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#fadb4e,#fceda7);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fadb4e),color-stop(1,#fceda7));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fadb4e', EndColorStr='#fceda7')"}.ui-btn-down-e a.ui-link-inherit{color:#333}.ui-btn-up-e,.ui-btn-hover-e,.ui-btn-down-e{font-family:Helvetica,Arial,sans-serif}a.ui-link-inherit{text-decoration:none!important}.ui-btn-active{border:1px solid #155678;background:#4596ce;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 -1px 1px #145072;text-decoration:none;background-image:-moz-linear-gradient(top,#85bae4,#5393c5);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#85bae4),color-stop(1,#5393c5));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#85bae4', EndColorStr='#5393c5')";outline:0}.ui-btn-active a.ui-link-inherit{color:#fff}.ui-btn-inner{border-top:1px solid #fff;border-color:rgba(255,255,255,.3)}.ui-corner-tl{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em}.ui-corner-tr{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bl{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-br{-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-top{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bottom{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-right{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-left{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-all{-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{cursor:default!important}.ui-icon{background-image:url(images/icons-18-white.png);background-repeat:no-repeat;background-color:#666;background-color:rgba(0,0,0,.4);-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-disc{background-color:#666;background-color:rgba(0,0,0,.3);-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-black{background-image:url(images/icons-18-black.png)}.ui-icon-black-disc{background-color:#fff;background-color:rgba(255,255,255,.3);-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}@media screen and (-webkit-min-device-pixel-ratio:2),screen and (max--moz-device-pixel-ratio:2){.ui-icon{background-image:url(images/icons-36-white.png);background-size:630px 18px}.ui-icon-black{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 0}.ui-icon-minus{background-position:-36px 0}.ui-icon-delete{background-position:-72px 0}.ui-icon-arrow-r{background-position:-108px 0}.ui-icon-arrow-l{background-position:-144px 0}.ui-icon-arrow-u{background-position:-180px 0}.ui-icon-arrow-d{background-position:-216px 0}.ui-icon-check{background-position:-252px 0}.ui-icon-gear{background-position:-288px 0}.ui-icon-refresh{background-position:-324px 0}.ui-icon-forward{background-position:-360px 0}.ui-icon-back{background-position:-396px 0}.ui-icon-grid{background-position:-432px 0}.ui-icon-star{background-position:-468px 0}.ui-icon-alert{background-position:-504px 0}.ui-icon-info{background-position:-540px 0}.ui-icon-home{background-position:-576px 0}.ui-icon-search{background-position:-612px 0}.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-color:transparent;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;background-size:20px 20px}.ui-icon-checkbox-off{background-image:url(images/form-check-off.png)}.ui-icon-checkbox-on{background-image:url(images/form-check-on.png)}.ui-icon-radio-off{background-image:url(images/form-radio-off.png)}.ui-icon-radio-on{background-image:url(images/form-radio-on.png)}.ui-icon-searchfield{background-image:url(images/icon-search-black.png);background-size:16px 16px}.ui-icon-loading{background-image:url(images/ajax-loader.png);width:40px;height:40px;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;background-size:35px 35px}.ui-btn-corner-tl{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em}.ui-btn-corner-tr{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bl{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-br{-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-top{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bottom{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-right{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-left{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-all{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}.ui-corner-tl,.ui-corner-tr,.ui-corner-bl,.ui-corner-br,.ui-corner-top,.ui-corner-bottom,.ui-corner-right,.ui-corner-left,.ui-corner-all,.ui-btn-corner-tl,.ui-btn-corner-tr,.ui-btn-corner-bl,.ui-btn-corner-br,.ui-btn-corner-top,.ui-btn-corner-bottom,.ui-btn-corner-right,.ui-btn-corner-left,.ui-btn-corner-all{-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.ui-overlay{background:#666;opacity:.5;filter:Alpha(Opacity=50);position:absolute;width:100%;height:100%}.ui-overlay-shadow{-moz-box-shadow:0 0 12px rgba(0,0,0,.6);-webkit-box-shadow:0 0 12px rgba(0,0,0,.6);box-shadow:0 0 12px rgba(0,0,0,.6)}.ui-shadow{-moz-box-shadow:0 1px 4px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 4px rgba(0,0,0,.3);box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-bar-a .ui-shadow,.ui-bar-b .ui-shadow,.ui-bar-c .ui-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.ui-shadow-inset{-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);box-shadow:inset 0 1px 4px rgba(0,0,0,.2)}.ui-icon-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.4);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.4);box-shadow:0 1px 0 rgba(255,255,255,.4)}.ui-focus{-moz-box-shadow:0 0 12px #387bbe;-webkit-box-shadow:0 0 12px #387bbe;box-shadow:0 0 12px #387bbe}.ui-mobile-nosupport-boxshadow *{-moz-box-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}.ui-mobile-nosupport-boxshadow .ui-focus{outline-width:2px}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border:0}.ui-mobile-viewport{margin:0;overflow-x:hidden;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}[data-role=page],[data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-page-active{display:block;overflow:visible}.portrait,.portrait .ui-page{min-height:480px}.landscape,.landscape .ui-page{min-height:320px}.ui-loading .ui-mobile-viewport{overflow:hidden!important}.ui-loading .ui-loader{display:block}.ui-loading .ui-page{overflow:hidden}.ui-loader{display:none;position:absolute;opacity:.85;z-index:10;left:50%;width:200px;margin-left:-130px;margin-top:-35px;padding:10px 30px}.ui-loader h1{font-size:15px;text-align:center}.ui-loader .ui-icon{position:static;display:block;opacity:.9;margin:0 auto;width:35px;height:35px;background-color:transparent}.ui-mobile-rendering>*{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{display:block}.ui-page .ui-header,.ui-page .ui-footer{position:relative}.ui-header .ui-btn-left{position:absolute;left:10px;top:.4em}.ui-header .ui-title,.ui-footer .ui-title{text-align:center;font-size:16px;display:block;margin:.6em 90px .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-header .ui-btn-right{position:absolute;right:10px;top:.4em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-page-fullscreen .ui-content{padding:0}.ui-icon{width:18px;height:18px}.ui-fullscreen img{max-width:100%}.ui-nojs{position:absolute;left:-9999px}.spin{-webkit-transform:rotate(360deg);-webkit-animation-name:spin;-webkit-animation-duration:1s;-webkit-animation-iteration-count:infinite}@-webkit-keyframes spin{from{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}.in,.out{-webkit-animation-timing-function:ease-in-out;-webkit-animation-duration:350ms}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;z-index:10}.slideup.out{-webkit-animation-name:dontmove;z-index:0}.slideup.out.reverse{-webkit-transform:translateY(100%);z-index:10;-webkit-animation-name:slideouttobottom}.slideup.in.reverse{z-index:0;-webkit-animation-name:dontmove}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;z-index:10}.slidedown.out{-webkit-animation-name:dontmove;z-index:0}.slidedown.out.reverse{-webkit-transform:translateY(-100%);z-index:10;-webkit-animation-name:slideouttotop}.slidedown.in.reverse{z-index:0;-webkit-animation-name:dontmove}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.in{opacity:1;z-index:10;-webkit-animation-name:fadein}.fade.out{z-index:0;-webkit-animation-name:fadeout}.ui-mobile-viewport-perspective{-webkit-perspective:1000;position:absolute}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden}.flip{-webkit-animation-duration:.65s;-webkit-backface-visibility:hidden;-webkit-transform:translateX(0)}.flip.in{-webkit-transform:rotateY(0) scale(1);-webkit-animation-name:flipinfromleft}.flip.out{-webkit-transform:rotateY(-180deg) scale(.8);-webkit-animation-name:flipouttoleft}.flip.in.reverse{-webkit-transform:rotateY(0) scale(1);-webkit-animation-name:flipinfromright}.flip.out.reverse{-webkit-transform:rotateY(180deg) scale(.8);-webkit-animation-name:flipouttoright}@-webkit-keyframes flipinfromright{from{-webkit-transform:rotateY(-180deg) scale(.8)}to{-webkit-transform:rotateY(0) scale(1)}}@-webkit-keyframes flipinfromleft{from{-webkit-transform:rotateY(180deg) scale(.8)}to{-webkit-transform:rotateY(0) scale(1)}}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0) scale(1)}to{-webkit-transform:rotateY(-180deg) scale(.8)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0) scale(1)}to{-webkit-transform:rotateY(180deg) scale(.8)}}@-webkit-keyframes dontmove{from{opacity:1}to{opacity:1}}.pop{-webkit-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);opacity:1;-webkit-animation-name:popin;z-index:10}.pop.out.reverse{-webkit-transform:scale(.2);opacity:0;-webkit-animation-name:popout;z-index:10}.pop.in.reverse{z-index:0;-webkit-animation-name:dontmove}@-webkit-keyframes popin{from{-webkit-transform:scale(.2);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.2);opacity:0}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:50%}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.333%}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:25%}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header,.ui-footer,.ui-page-fullscreen .ui-header,.ui-page-fullscreen .ui-footer{position:absolute;overflow:hidden;width:100%;border-left-width:0;border-right-width:0}.ui-header-fixed,.ui-footer-fixed{z-index:1000;-webkit-transform:translateZ(0)}.ui-footer-duplicate,.ui-page-fullscreen .ui-fixed-inline{display:none}.ui-page-fullscreen .ui-header,.ui-page-fullscreen .ui-footer{opacity:.9}.ui-navbar{overflow:hidden}.ui-navbar ul,.ui-navbar-expanded ul{list-style:none;padding:0;margin:0;position:relative;display:block;border:0}.ui-navbar-collapsed ul{float:left;width:75%;margin-right:-2px}.ui-navbar-collapsed .ui-navbar-toggle{float:left;width:25%}.ui-navbar li.ui-navbar-truncate{position:absolute;left:-9999px;top:-9999px}.ui-navbar li .ui-btn,.ui-navbar .ui-navbar-toggle .ui-btn{display:block;font-size:12px;text-align:center;margin:0;border-right-width:0}.ui-navbar li .ui-btn{margin-right:-1px}.ui-navbar li .ui-btn:last-child{margin-right:0}.ui-header .ui-navbar li .ui-btn,.ui-header .ui-navbar .ui-navbar-toggle .ui-btn,.ui-footer .ui-navbar li .ui-btn,.ui-footer .ui-navbar .ui-navbar-toggle .ui-btn{border-top-width:0;border-bottom-width:0}.ui-navbar .ui-btn-inner{padding-left:2px;padding-right:2px}.ui-navbar-noicons li .ui-btn .ui-btn-inner,.ui-navbar-noicons .ui-navbar-toggle .ui-btn-inner{padding-top:.8em;padding-bottom:.9em}.ui-navbar-expanded .ui-btn{margin:0;font-size:14px}.ui-navbar-expanded .ui-btn-inner{padding-left:5px;padding-right:5px}.ui-navbar-expanded .ui-btn-icon-top .ui-btn-inner{padding:45px 5px 15px;text-align:center}.ui-navbar-expanded .ui-btn-icon-top .ui-icon{top:15px}.ui-navbar-expanded .ui-btn-icon-bottom .ui-btn-inner{padding:15px 5px 45px;text-align:center}.ui-navbar-expanded .ui-btn-icon-bottom .ui-icon{bottom:15px}.ui-navbar-expanded li .ui-btn .ui-btn-inner{min-height:2.5em}.ui-navbar-expanded .ui-navbar-noicons .ui-btn .ui-btn-inner{padding-top:1.8em;padding-bottom:1.9em}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 5px;padding:0}.ui-btn:focus,.ui-btn:active{outline:0}.ui-header .ui-btn,.ui-footer .ui-btn,.ui-bar .ui-btn{display:inline-block;font-size:13px;margin:0}.ui-btn-inline{display:inline-block}.ui-btn-inner{padding:.6em 25px;display:block;height:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-bar .ui-btn-inner{padding:.4em 8px .5em}.ui-btn-icon-notext{display:inline-block;width:20px;height:20px;padding:2px 1px 2px 3px;text-indent:-9999px}.ui-btn-icon-notext .ui-btn-inner{padding:0}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-999px}.ui-btn-icon-left .ui-btn-inner{padding-left:33px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-bar .ui-btn-icon-left .ui-btn-inner{padding-left:27px}.ui-btn-icon-right .ui-btn-inner{padding-right:33px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-bar .ui-btn-icon-right .ui-btn-inner{padding-right:27px}.ui-btn-icon-top .ui-btn-inner{padding-top:33px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner,.ui-bar .ui-btn-icon-top .ui-btn-inner{padding-top:27px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:33px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner,.ui-bar .ui-btn-icon-bottom .ui-btn-inner{padding-bottom:27px}.ui-btn-icon-notext .ui-icon{display:block}.ui-btn-icon-left .ui-icon,.ui-btn-icon-right .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-icon,.ui-btn-icon-bottom .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-bar .ui-btn-icon-left .ui-icon{left:4px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-bar .ui-btn-icon-right .ui-icon{right:4px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-bar .ui-btn-icon-top .ui-icon{top:4px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-bar .ui-btn-icon-bottom .ui-icon{bottom:4px}.ui-btn-icon-top .ui-icon{top:5px}.ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:button;opacity:0;cursor:pointer}.ui-collapsible-contain{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -8px;padding:0;border-width:0 0 1px 0;position:relative}.ui-collapsible-heading a{text-align:left;margin:0}.ui-collapsible-heading a .ui-btn-inner{padding-left:40px}.ui-collapsible-heading a span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading a span.ui-btn .ui-btn-inner{padding:0}.ui-collapsible-heading a span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;left:-9999px}.ui-collapsible-content{display:block;padding:10px 0 10px 8px}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible-contain{margin:-1px 0 0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:.5em 0 1em}.ui-bar .ui-controlgroup{margin:0 .3em}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em}.ui-controlgroup-controls{display:block;width:95%}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-horizontal{padding:0}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{margin:0 -5px 0 0;display:inline-block}.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup-horizontal .ui-controlgroup-last{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}.min-width-480px .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.min-width-480px .ui-controlgroup-controls{width:60%;display:inline-block}.ui-dialog{min-height:480px}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{margin:15px;position:relative}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10;width:auto}.ui-dialog .ui-content,.ui-dialog .ui-footer{margin-top:-15px}.ui-checkbox,.ui-radio{position:relative;margin:.2em 0 .5em;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin:0;text-align:left;z-index:2}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain{background:0;padding:1.5em 0;margin:0;border-bottom-width:1px;overflow:visible}.ui-field-contain:first-child{border-top-width:0}.min-width-480px .ui-field-contain{border-width:0;padding:0;margin:1em 0}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:button;left:0;top:0;width:100%;height:100%;opacity:.001}.ui-select .ui-btn select.ui-select-nativeonly{opacity:1}.ui-select .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:inline-block;min-height:1em}.ui-select .ui-btn-text{text-overflow:ellipsis;overflow:hidden;width:85%}.ui-selectmenu{position:absolute;padding:0;z-index:100!important;width:80%;max-width:350px;padding:6px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-9999px;left:-9999px}.ui-selectmenu-screen{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.min-width-480px label.ui-select{display:inline-block;width:20%;margin:0 2% 0 0}.min-width-480px .ui-select{width:60%;display:inline-block}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;line-height:1.4;font-size:16px;display:block;width:95%}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;width:77%;background-position:8px 50%;background-repeat:no-repeat;position:relative}.ui-input-search input.ui-input-text{border:0;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-14px}.ui-input-search .ui-input-clear-hidden{display:none}.min-width-480px label.ui-input-text{vertical-align:top}.min-width-480px label.ui-input-text{display:inline-block;width:20%;margin:0 2% 0 0}.min-width-480px input.ui-input-text,.min-width-480px textarea.ui-input-text,.min-width-480px .ui-input-search{width:60%;display:inline-block}.min-width-480px .ui-input-search{width:50%}.ui-listview{margin:0;counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-listview,.ui-li{list-style:none;padding:0;zoom:1}.ui-li{display:block;margin:0;position:relative;overflow:hidden;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold;counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li:last-child{border-bottom-width:1px}.ui-li .ui-btn-inner{display:block;position:relative;padding:.7em 75px .7em 15px}.ui-li-has-thumb .ui-btn-inner{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner{min-height:20px;padding-left:40px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-li-icon{max-height:40px;max-width:40px;left:10px;top:.9em}.ui-li-thumb,.ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}.min-width-480px .ui-li-aside{width:45%}.ui-li-has-alt .ui-btn-inner{padding-right:95px}.ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:38px}.ui-li-divider .ui-li-count{right:10px}.ui-li-has-alt .ui-li-count{right:55px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-11px 0 0 0;border-bottom-width:1px}.ui-li-link-alt .ui-btn-inner{padding:0;position:static}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{display:block}input.ui-slider-input,.min-width-480px input.ui-slider-input{display:inline-block;width:50px}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:66%}a.ui-slider-handle{position:absolute;z-index:10;top:50%;width:28px;height:28px;margin-top:-15px;margin-left:-15px}a.ui-slider-handle .ui-btn-inner{padding-left:0;padding-right:0}.min-width-480px label.ui-slider{display:inline-block;width:20%;margin:0 2% 0 0}.min-width-480px div.ui-slider{width:45%}div.ui-slider-switch{height:32px;overflow:hidden;margin-left:0}div.ui-slider-inneroffset{margin-left:50%;position:absolute;top:1px;height:100%;width:50%}div.ui-slider-handle-snapping{-webkit-transition:left 100ms linear}div.ui-slider-labelbg{position:absolute;top:0;margin:0;border-width:0}div.ui-slider-switch div.ui-slider-labelbg-a{width:60%;height:100%;left:0}div.ui-slider-switch div.ui-slider-labelbg-b{width:60%;height:100%;right:0}.ui-slider-switch-a div.ui-slider-labelbg-a,.ui-slider-switch-b div.ui-slider-labelbg-b{z-index:1}.ui-slider-switch-a div.ui-slider-labelbg-b,.ui-slider-switch-b div.ui-slider-labelbg-a{z-index:10}div.ui-slider-switch a.ui-slider-handle{z-index:20;width:101%;height:32px;margin-top:-18px;margin-left:-101%}span.ui-slider-label{width:100%;position:absolute;height:32px;font-size:16px;text-align:center;line-height:2;background:0;border-color:transparent}span.ui-slider-label-a{left:-100%;margin-right:-1px}span.ui-slider-label-b{right:-100%;margin-left:-1px}
+

--- /dev/null
+++ b/css/jquery.mobile-1.0a4.css
@@ -1,1 +1,1661 @@
-
+/*!
+ * jQuery Mobile v1.0a4
+ * http://jquerymobile.com/
+ *
+ * Copyright 2010, jQuery Project
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ */
+/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* Note: Code is in draft form and is subject to change 
+*/
+
+
+/* A
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-bar-a {
+	border: 1px solid 		#2A2A2A;
+	background: 			#111111;
+	color: 					#ffffff;
+	font-weight: bold;
+	text-shadow: 0 -1px 1px #000000;
+	background-image: -moz-linear-gradient(top, 
+							#3c3c3c, 
+							#111111);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 	#3c3c3c),
+		color-stop(1, 		#111111));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#3c3c3c', EndColorStr='#111111')";
+}
+.ui-bar-a, 
+.ui-bar-a input, 
+.ui-bar-a select, 
+.ui-bar-a textarea, 
+.ui-bar-a button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-bar-a .ui-link-inherit {
+	color: 					#fff;
+}
+.ui-bar-a .ui-link {
+	color: 					#7cc4e7;
+	font-weight: bold;
+}
+.ui-body-a {
+	border: 1px solid 		#2A2A2A;
+	background: 			#222222;
+	color: 					#fff;
+	 text-shadow: 0 1px 0 	#000;
+	font-weight: normal;
+	background-image: -moz-linear-gradient(top, 
+							#666666, 
+							#222222);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#666666),
+		color-stop(1, 		#222222));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#666666', EndColorStr='#222222)')";
+}
+.ui-body-a,
+.ui-body-a input,
+.ui-body-a select,
+.ui-body-a textarea,
+.ui-body-a button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-body-a .ui-link-inherit {
+	color: 					#fff;
+}
+.ui-body-a .ui-link {
+	color: 					#2489CE;
+	font-weight: bold;
+}
+.ui-br {
+	border-bottom: rgb(130,130,130);
+	border-bottom: rgba(130,130,130,.3);
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+}
+.ui-btn-up-a {
+	border: 1px solid 		#222;
+	background: 			#333333;
+	font-weight: bold;
+	color: 					#fff;
+	text-shadow: 0 -1px 1px #000;
+	background-image: -moz-linear-gradient(top, 
+							#555555, 
+							#333333);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#555555),
+		color-stop(1, 		#333333));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#555555', EndColorStr='#333333')";
+}
+.ui-btn-up-a a.ui-link-inherit {
+	color: 					#fff;
+}
+.ui-btn-hover-a {
+	border: 1px solid 		#000;
+	background: 			#444444;
+	font-weight: bold;
+	color: 					#fff;
+	text-shadow: 0 -1px 1px #000;
+	background-image: -moz-linear-gradient(top, 
+							#666666, 
+							#444444);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#666666),
+		color-stop(1, 		#444444));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#666666', EndColorStr='#444444')";
+}
+.ui-btn-hover-a a.ui-link-inherit {
+	color: 					#fff;
+}
+.ui-btn-down-a {
+	border: 1px solid 		#000;
+	background: 			#3d3d3d;
+	font-weight: bold;
+	color: 					#fff;
+	text-shadow: 0 -1px 1px #000;
+	background-image: -moz-linear-gradient(top, 
+							#333333, 
+							#5a5a5a);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#333333),
+		color-stop(1, 		#5a5a5a));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#333333', EndColorStr='#5a5a5a')";
+}
+.ui-btn-down-a a.ui-link-inherit {
+	color: 					#fff;
+}
+.ui-btn-up-a,
+.ui-btn-hover-a,
+.ui-btn-down-a {
+	font-family: Helvetica, Arial, sans-serif;
+	text-decoration: none;
+}
+
+
+/* B
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-bar-b {
+	border: 1px solid 		#456f9a;
+	background: 			#5e87b0;
+	color: 					#fff;
+	font-weight: bold;
+	text-shadow: 0 -1px 1px #254f7a;
+	background-image: -moz-linear-gradient(top, 
+							#81a8ce, 
+							#5e87b0);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#81a8ce),
+		color-stop(1, 		#5e87b0));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#81a8ce', EndColorStr='#5e87b0')";
+}
+.ui-bar-b,
+.ui-bar-b input,
+.ui-bar-b select,
+.ui-bar-b textarea,
+.ui-bar-b button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-bar-b .ui-link-inherit {
+	color: 					#fff;
+}
+.ui-bar-b .ui-link {
+	color: 					#7cc4e7;
+	font-weight: bold;
+}
+
+.ui-body-b {
+	border: 1px solid 		#C6C6C6;
+	background: 			#cccccc;
+	color: 					#333333;
+	text-shadow: 0 1px 0 	#fff;
+	font-weight: normal;
+	background-image: -moz-linear-gradient(top, 
+							#e6e6e6, 
+							#cccccc);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#e6e6e6),
+		color-stop(1, 		#cccccc));
+	 -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#e6e6e6', EndColorStr='#cccccc')";
+}
+.ui-body-b,
+.ui-body-b input,
+.ui-body-b select,
+.ui-body-b textarea,
+.ui-body-b button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-body-b .ui-link-inherit {
+	color: 					#333333;
+}
+.ui-body-b .ui-link {
+	color: 					#2489CE;
+	font-weight: bold;
+}
+.ui-btn-up-b {
+	border: 1px solid 		#145072;
+	background: 			#2567ab;
+	font-weight: bold;
+	color: 					#fff;
+	text-shadow: 0 -1px 1px #145072;
+	background-image: -moz-linear-gradient(top, 
+							#4e89c5, 
+							#2567ab);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+			color-stop(0, 	#5f9cc5),
+			color-stop(1, 	#396b9e));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#4e89c5', EndColorStr='#2567ab')";
+}
+.ui-btn-up-b a.ui-link-inherit {
+	color: 					#fff;
+}
+.ui-btn-hover-b {
+	border: 1px solid 		#00516e;
+	background: 			#4b88b6;
+	font-weight: bold;
+	color: 					#fff;
+	text-shadow: 0 -1px 1px #014D68;
+	background-image: -moz-linear-gradient(top, 
+							#72b0d4, 
+							#4b88b6);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+			color-stop(0, 	#72b0d4),
+			color-stop(1, 	#4b88b6));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#72b0d4', EndColorStr='#4b88b6')";
+}
+.ui-btn-hover-b a.ui-link-inherit {
+	color: 					#fff;
+}
+.ui-btn-down-b {
+	border: 1px solid 		#225377;
+	background: 			#4e89c5;
+	font-weight: bold;
+	color: 					#fff;
+	text-shadow: 0 -1px 1px #225377;
+	background-image: -moz-linear-gradient(top, 
+							#396b9e, 
+							#4e89c5);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#396b9e),
+		color-stop(1, 		#4e89c5));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#396b9e', EndColorStr='#4e89c5')";
+}
+.ui-btn-down-b a.ui-link-inherit {
+	color: 					#fff;
+}
+.ui-btn-up-b,
+.ui-btn-hover-b,
+.ui-btn-down-b {
+	font-family: Helvetica, Arial, sans-serif;
+	text-decoration: none;
+}
+
+
+/* C
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-bar-c {
+	border: 1px solid 		#B3B3B3;
+	background: 			#e9eaeb;
+	color: 					#3E3E3E;
+	font-weight: bold;
+	text-shadow: 0 1px 1px 	#fff;
+	background-image: -moz-linear-gradient(top, 
+							#f0f0f0,
+							#e9eaeb);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+			color-stop(0, 	#f0f0f0),
+			color-stop(1, 	#e9eaeb));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#f0f0f0', EndColorStr='#e9eaeb')";
+}
+.ui-bar-c,
+.ui-bar-c input,
+.ui-bar-c select,
+.ui-bar-c textarea,
+.ui-bar-c button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-body-c {
+	border: 1px solid 		#B3B3B3;
+	color: 					#333333;
+	text-shadow: 0 1px 0 	#fff;
+	background: 			#f0f0f0;
+	background-image: -moz-linear-gradient(top, 
+							#eeeeee, 
+							#dddddd);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#eeeeee),
+		color-stop(1, 		#dddddd));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#dddddd')";
+}
+.ui-body-c,
+.ui-body-c input,
+.ui-body-c select,
+.ui-body-c textarea,
+.ui-body-c button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-body-c .ui-link-inherit {
+	color: 					#333333;
+}
+.ui-body-c .ui-link {
+	color: 					#2489CE;
+	font-weight: bold;
+}
+
+.ui-btn-up-c {
+	border: 1px solid 		#ccc;
+	background: 			#eee;
+	font-weight: bold;
+	color: 					#444;
+	text-shadow: 0 1px 1px #f6f6f6;
+	background-image: -moz-linear-gradient(top, 
+							#fefefe, 
+							#eeeeee);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#fdfdfd),
+		color-stop(1, 		#eeeeee));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fdfdfd', EndColorStr='#eeeeee')";
+}
+.ui-btn-up-c a.ui-link-inherit {
+	color: 					#2F3E46;
+}
+
+.ui-btn-hover-c {
+	border: 1px solid 		#bbb;
+	background: 			#dadada;
+	font-weight: bold;
+	color: 					#101010;
+	text-shadow: 0 1px 1px 	#fff;
+	background-image: -moz-linear-gradient(top, 
+							#ededed, 
+							#dadada);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#ededed),
+		color-stop(1, 		#dadada));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#ededed', EndColorStr='#dadada')";
+}
+.ui-btn-hover-c a.ui-link-inherit {
+	color: 					#2F3E46;
+}
+.ui-btn-down-c {
+	border: 1px solid 		#808080;
+	background: 			#fdfdfd;
+	font-weight: bold;
+	color: 					#111111;
+	text-shadow: 0 1px 1px 	#ffffff;
+	background-image: -moz-linear-gradient(top, 
+							#eeeeee, 
+							#fdfdfd);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#eeeeee),
+		color-stop(1, 		#fdfdfd));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#fdfdfd')";
+}
+.ui-btn-down-c a.ui-link-inherit {
+	color: 					#2F3E46;
+}
+.ui-btn-up-c,
+.ui-btn-hover-c,
+.ui-btn-down-c {
+	font-family: Helvetica, Arial, sans-serif;
+	text-decoration: none;
+}
+
+
+/* D
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-bar-d {
+	border: 1px solid 		#ccc;
+	background: 			#bbb;
+	color: 					#333;
+	text-shadow: 0 1px 0 #eee;
+	background-image: -moz-linear-gradient(top, 
+							#ddd, 
+							#bbb);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#ddd),
+		color-stop(1, 		#bbb));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#dddddd', EndColorStr='#bbbbbb')";
+}
+.ui-bar-d,
+.ui-bar-d input,
+.ui-bar-d select,
+.ui-bar-d textarea,
+.ui-bar-d button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-bar-d .ui-link-inherit {
+	color: 					#333;
+}
+.ui-bar-d .ui-link {
+	color: 					#2489CE;
+	font-weight: bold;
+}
+.ui-body-d {
+	border: 1px solid 		#ccc;
+	color: 					#333333;
+	text-shadow: 0 1px 0 	#fff;
+	background: 			#ffffff;
+}
+.ui-body-d,
+.ui-body-d input,
+.ui-body-d select,
+.ui-body-d textarea,
+.ui-body-d button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-body-d .ui-link-inherit {
+	color: 					#333333;
+}
+.ui-body-d .ui-link {
+	color: 					#2489CE;
+	font-weight: bold;
+}
+.ui-btn-up-d {
+	border: 1px solid 		#ccc;
+	background: 			#fff;
+	font-weight: bold;
+	color: 					#444;
+	text-shadow: 0 1px 1px #fff;
+}
+.ui-btn-up-d a.ui-link-inherit {
+	color: 					#333;
+}
+.ui-btn-hover-d {
+	border: 1px solid 		#aaa;
+	background: 			#eeeeee;
+	font-weight: bold;
+	color: 					#222;
+	cursor: pointer;
+	text-shadow: 0 1px 1px 	#fff;
+	background-image: -moz-linear-gradient(top, 
+							#fdfdfd, 
+							#eeeeee);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#fdfdfd),
+		color-stop(1, 		#eeeeee));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fdfdfd', EndColorStr='#eeeeee')";
+}
+.ui-btn-hover-d a.ui-link-inherit {
+	color: 					#222;
+}
+.ui-btn-down-d {
+	border: 1px solid 		#aaaaaa;
+	background: 			#ffffff;
+	font-weight: bold;
+	color: 					#111;
+	text-shadow: 0 1px 1px 	#ffffff;
+	background-image: -moz-linear-gradient(top, 
+							#eeeeee, 
+							#ffffff);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#eeeeee),
+		color-stop(1, 		#ffffff));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#ffffff')";
+}
+.ui-btn-down-d a.ui-link-inherit {
+	border: 1px solid 		#808080;
+	background: 			#ced0d2;
+	font-weight: bold;
+	color: 					#111;
+	text-shadow: none;
+	background-image: -moz-linear-gradient(top, 
+							#cccccc, 
+							#eeeeee);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#cccccc),
+		color-stop(1, 		#eeeeee));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#cccccc', EndColorStr='#eeeeee')";
+}
+.ui-btn-up-d,
+.ui-btn-hover-d,
+.ui-btn-down-d {
+	font-family: Helvetica, Arial, sans-serif;
+	text-decoration: none;
+}
+
+
+/* E
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-bar-e {
+	border: 1px solid 		#F7C942;
+	background: 			#fadb4e;
+	color: 					#333;
+	text-shadow: 0 1px 0 	#fff;
+	background-image: -moz-linear-gradient(top, 
+							#fceda7, 
+							#fadb4e);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#fceda7),
+		color-stop(1, 		#fadb4e));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fceda7', EndColorStr='#fadb4e')";
+}
+.ui-bar-e,
+.ui-bar-e input,
+.ui-bar-e select,
+.ui-bar-e textarea,
+.ui-bar-d button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-bar-e .ui-link-inherit {
+	color: 					#333;
+}
+.ui-bar-e .ui-link {
+	color: 					#2489CE;
+	font-weight: bold;
+}
+.ui-body-e {
+	border: 1px solid 		#F7C942;
+	color: 					#333333;
+	text-shadow: 0 1px 0 	#fff;
+	background: 			#faeb9e;
+	background-image: -moz-linear-gradient(top, 
+							#fff, 
+							#faeb9e);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#fff),
+		color-stop(1, 		#faeb9e));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#faeb9e')";
+}
+.ui-body-e,
+.ui-body-e input,
+.ui-body-e select,
+.ui-body-e textarea,
+.ui-body-e button {
+	font-family: Helvetica, Arial, sans-serif;
+}
+.ui-body-e .ui-link-inherit {
+	color: 					#333333;
+}
+.ui-body-e .ui-link {
+	color: 					#2489CE;
+	font-weight: bold;
+}
+.ui-btn-up-e {
+	border: 1px solid 		#F7C942;
+	background: 			#fadb4e;
+	font-weight: bold;
+	color: 					#333;
+	text-shadow: 0 1px 1px 	#fe3;
+	text-shadow: 0 1px 0 	#fff;
+	background-image: -moz-linear-gradient(top, 
+							#fceda7, 
+							#fadb4e);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#fceda7),
+		color-stop(1, 		#fadb4e));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fceda7', EndColorStr='#fadb4e')";
+}
+.ui-btn-up-e a.ui-link-inherit {
+	color: 					#333;
+}
+.ui-btn-hover-e {
+	border: 1px solid 		#e79952;
+	background: 			#fbe26f;
+	font-weight: bold;
+	color: 					#111;
+	text-shadow: 0 1px 1px 	#fff;
+	background-image: -moz-linear-gradient(top, 
+							#fcf0b5, 
+							#fbe26f);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#fcf0b5),
+		color-stop(1, 		#fbe26f));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fcf0b5', EndColorStr='#fbe26f')";
+}
+
+.ui-btn-hover-e a.ui-link-inherit {
+	color: 					#333;
+}
+.ui-btn-down-e {
+	border: 1px solid 		#F7C942;
+	background: 			#fceda7;
+	font-weight: bold;
+	color: 					#111;
+	text-shadow: 0 1px 1px 	#ffffff;
+	background-image: -moz-linear-gradient(top, 
+							#fadb4e, 
+							#fceda7);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#fadb4e),
+		color-stop(1, 		#fceda7));
+	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fadb4e', EndColorStr='#fceda7')";
+}
+.ui-btn-down-e a.ui-link-inherit {
+	color: 					#333;
+}
+.ui-btn-up-e,
+.ui-btn-hover-e,
+.ui-btn-down-e {
+	font-family: Helvetica, Arial, sans-serif;
+	text-decoration: none;
+}
+
+
+/* links within "buttons" 
+-----------------------------------------------------------------------------------------------------------*/
+
+a.ui-link-inherit {
+	text-decoration: none !important;
+}
+
+
+/* Active class used as the "on" state across all themes
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-btn-active {
+	border: 1px solid 		#155678;
+	background: 			#4596ce;
+	font-weight: bold;
+	color: 					#fff;
+	cursor: pointer;
+	text-shadow: 0 -1px 1px #145072;
+	text-decoration: none;
+	background-image: -moz-linear-gradient(top, 
+							#85bae4, 
+							#5393c5);
+	background-image: -webkit-gradient(linear,left top,left bottom,
+		color-stop(0, 		#85bae4),
+		color-stop(1, 		#5393c5));
+  	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#85bae4', EndColorStr='#5393c5')";
+  	outline: none;
+}
+.ui-btn-active a.ui-link-inherit {
+	color: 					#fff;
+}
+
+
+/* button inner top highlight
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-btn-inner {
+	border-top: 1px solid 	#fff;
+	border-color: 			rgba(255,255,255,.3);
+}
+
+
+/* corner rounding classes
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-corner-tl {
+	-moz-border-radius-topleft: 		.6em;
+	-webkit-border-top-left-radius: 	.6em;
+	border-top-left-radius: 			.6em;
+}
+.ui-corner-tr {
+	-moz-border-radius-topright: 		.6em;
+	-webkit-border-top-right-radius: 	.6em;
+	border-top-right-radius: 			.6em;
+}
+.ui-corner-bl {
+	-moz-border-radius-bottomleft: 		.6em;
+	-webkit-border-bottom-left-radius: 	.6em;
+	border-bottom-left-radius: 			.6em;
+}
+.ui-corner-br {
+	-moz-border-radius-bottomright: 	.6em;
+	-webkit-border-bottom-right-radius: .6em;
+	border-bottom-right-radius: 		.6em;
+}
+.ui-corner-top {
+	-moz-border-radius-topleft: 		.6em;
+	-webkit-border-top-left-radius: 	.6em;
+	border-top-left-radius: 			.6em;
+	-moz-border-radius-topright: 		.6em;
+	-webkit-border-top-right-radius: 	.6em;
+	border-top-right-radius: 			.6em;
+}
+.ui-corner-bottom {
+	-moz-border-radius-bottomleft: 		.6em;
+	-webkit-border-bottom-left-radius: 	.6em;
+	border-bottom-left-radius: 			.6em;
+	-moz-border-radius-bottomright: 	.6em;
+	-webkit-border-bottom-right-radius: .6em;
+	border-bottom-right-radius: 		.6em;
+	}
+.ui-corner-right {
+	-moz-border-radius-topright: 		.6em;
+	-webkit-border-top-right-radius: 	.6em;
+	border-top-right-radius: 			.6em;
+	-moz-border-radius-bottomright: 	.6em;
+	-webkit-border-bottom-right-radius: .6em;
+	border-bottom-right-radius: 		.6em;
+}
+.ui-corner-left {
+	-moz-border-radius-topleft: 		.6em;
+	-webkit-border-top-left-radius: 	.6em;
+	border-top-left-radius: 			.6em;
+	-moz-border-radius-bottomleft: 		.6em;
+	-webkit-border-bottom-left-radius: 	.6em;
+	border-bottom-left-radius: 			.6em;
+}
+.ui-corner-all {
+	-moz-border-radius: 				.6em;
+	-webkit-border-radius: 				.6em;
+	border-radius: 						.6em;
+}
+
+
+
+/* Interaction cues
+-----------------------------------------------------------------------------------------------------------*/
+.ui-disabled {
+	opacity: 							.3;
+}
+.ui-disabled,
+.ui-disabled a {
+	cursor: default !important;
+}
+
+/* Icons
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-icon {
+	background: 						#666;
+	background: 						rgba(0,0,0,.4);
+	background-image: url(images/icons-18-white.png);
+	background-repeat: no-repeat;
+	-moz-border-radius: 				9px;
+	-webkit-border-radius: 				9px;
+	border-radius: 						9px;
+}
+
+
+/* Alt icon color
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-icon-alt {
+	background: 						#fff;
+	background: 						rgba(255,255,255,.3);
+	background-image: url(images/icons-18-black.png);
+	background-repeat: no-repeat;
+}
+
+/* HD/"retina" sprite
+-----------------------------------------------------------------------------------------------------------*/
+
+@media only screen and (-webkit-min-device-pixel-ratio: 1.5),
+       only screen and (min--moz-device-pixel-ratio: 1.5),
+       only screen and (min-resolution: 240dpi) {
+	
+	.ui-icon-plus, .ui-icon-minus, .ui-icon-delete, .ui-icon-arrow-r,
+	.ui-icon-arrow-l, .ui-icon-arrow-u, .ui-icon-arrow-d, .ui-icon-check,
+	.ui-icon-gear, .ui-icon-refresh, .ui-icon-forward, .ui-icon-back,
+	.ui-icon-grid, .ui-icon-star, .ui-icon-alert, .ui-icon-info, .ui-icon-home, .ui-icon-search, 
+	.ui-icon-checkbox-off, .ui-icon-checkbox-on, .ui-icon-radio-off, .ui-icon-radio-on {
+		background-image: url(images/icons-36-white.png);
+		-moz-background-size: 776px 18px;
+		-o-background-size: 776px 18px;
+		-webkit-background-size: 776px 18px;
+		background-size: 776px 18px;
+	}
+	.ui-icon-alt {
+		background-image: url(images/icons-36-black.png);
+	}
+}
+
+/* plus minus */
+.ui-icon-plus {
+	background-position: 	-0 50%;
+}
+.ui-icon-minus {
+	background-position: 	-36px 50%;
+}
+
+/* delete/close */
+.ui-icon-delete {
+	background-position: 	-72px 50%;
+}
+
+/* arrows */
+.ui-icon-arrow-r {
+	background-position: 	-108px 50%;
+}
+.ui-icon-arrow-l {
+	background-position: 	-144px 50%;
+}
+.ui-icon-arrow-u {
+	background-position: 	-180px 50%;
+}
+.ui-icon-arrow-d {
+	background-position: 	-216px 50%;
+}
+
+/* misc */
+.ui-icon-check {
+	background-position: 	-252px 50%;
+}
+.ui-icon-gear {
+	background-position: 	-288px 50%;
+}
+.ui-icon-refresh {
+	background-position: 	-324px 50%;
+}
+.ui-icon-forward {
+	background-position: 	-360px 50%;
+}
+.ui-icon-back {
+	background-position: 	-396px 50%;
+}
+.ui-icon-grid {
+	background-position: 	-432px 50%;
+}
+.ui-icon-star {
+	background-position: 	-468px 50%;
+}
+.ui-icon-alert {
+	background-position: 	-504px 50%;
+}
+.ui-icon-info {
+	background-position: 	-540px 50%;
+}
+.ui-icon-home {
+	background-position: 	-576px 50%;
+}
+.ui-icon-search {
+	background-position: 	-612px 50%;
+}
+.ui-icon-checkbox-off {
+	background-position: 	-684px 50%;
+}
+.ui-icon-checkbox-on {
+	background-position: 	-648px 50%;
+}
+.ui-icon-radio-off {
+	background-position: 	-756px 50%;
+}
+.ui-icon-radio-on {
+	background-position: 	-720px 50%;
+}
+
+
+/* checks,radios */
+.ui-icon-checkbox-off,
+.ui-icon-checkbox-on,
+.ui-icon-radio-off,
+.ui-icon-radio-on {
+	background-color: transparent;
+	-moz-border-radius: 0;
+	-webkit-border-radius: 0;
+	border-radius: 0;
+}
+.ui-icon-searchfield {
+	background-image: url(images/icon-search-black.png);
+	background-size: 16px 16px;
+}
+
+/* loading icon */
+.ui-icon-loading {
+	background-image: url(images/ajax-loader.png);
+	width: 40px;
+	height: 40px;
+	-moz-border-radius: 20px;
+	-webkit-border-radius: 20px;
+	border-radius: 20px;
+	background-size: 35px 35px;
+}
+
+
+/* Button corner classes
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-btn-corner-tl {
+	-moz-border-radius-topleft: 		1em;
+	-webkit-border-top-left-radius: 	1em;
+	border-top-left-radius: 			1em;
+}
+.ui-btn-corner-tr {
+	-moz-border-radius-topright: 		1em;
+	-webkit-border-top-right-radius: 	1em;
+	border-top-right-radius: 			1em;
+}
+.ui-btn-corner-bl {
+	-moz-border-radius-bottomleft: 		1em;
+	-webkit-border-bottom-left-radius: 	1em;
+	border-bottom-left-radius: 			1em;
+}
+.ui-btn-corner-br {
+	-moz-border-radius-bottomright: 	1em;
+	-webkit-border-bottom-right-radius: 1em;
+	border-bottom-right-radius: 		1em;
+}
+.ui-btn-corner-top {
+	-moz-border-radius-topleft: 		1em;
+	-webkit-border-top-left-radius: 	1em;
+	border-top-left-radius: 			1em;
+	-moz-border-radius-topright: 		1em;
+	-webkit-border-top-right-radius: 	1em;
+	border-top-right-radius: 			1em;
+}
+.ui-btn-corner-bottom {
+	-moz-border-radius-bottomleft: 		1em;
+	-webkit-border-bottom-left-radius: 	1em;
+	border-bottom-left-radius: 			1em;
+	-moz-border-radius-bottomright: 	1em;
+	-webkit-border-bottom-right-radius: 1em;
+	border-bottom-right-radius: 		1em;
+}
+.ui-btn-corner-right {
+	 -moz-border-radius-topright: 		1em;
+	-webkit-border-top-right-radius: 	1em;
+	border-top-right-radius: 			1em;
+	-moz-border-radius-bottomright: 	1em;
+	-webkit-border-bottom-right-radius: 1em;
+	border-bottom-right-radius: 		1em;
+}
+.ui-btn-corner-left {
+	-moz-border-radius-topleft: 		1em;
+	-webkit-border-top-left-radius: 	1em;
+	border-top-left-radius: 			1em;
+	-moz-border-radius-bottomleft: 		1em;
+	-webkit-border-bottom-left-radius: 	1em;
+	border-bottom-left-radius: 			1em;
+}
+.ui-btn-corner-all {
+	-moz-border-radius: 				1em;
+	-webkit-border-radius: 				1em;
+	border-radius: 						1em;
+}
+
+/* radius clip workaround for cleaning up corner trapping */
+.ui-corner-tl,
+.ui-corner-tr,
+.ui-corner-bl, 
+.ui-corner-br,
+.ui-corner-top,
+.ui-corner-bottom, 
+.ui-corner-right,
+.ui-corner-left,
+.ui-corner-all,
+.ui-btn-corner-tl,
+.ui-btn-corner-tr,
+.ui-btn-corner-bl, 
+.ui-btn-corner-br,
+.ui-btn-corner-top,
+.ui-btn-corner-bottom, 
+.ui-btn-corner-right,
+.ui-btn-corner-left,
+.ui-btn-corner-all {
+  -webkit-background-clip: padding-box;
+     -moz-background-clip: padding-box;
+          background-clip: padding-box;
+}
+
+/* Overlay / modal
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-overlay {
+	background: #666;
+	opacity: .5;
+	filter: Alpha(Opacity=50);
+	position: absolute;
+	width: 100%;
+	height: 100%;
+}
+.ui-overlay-shadow {
+	-moz-box-shadow: 0px 0px 12px 			rgba(0,0,0,.6);
+	-webkit-box-shadow: 0px 0px 12px 		rgba(0,0,0,.6);
+	box-shadow: 0px 0px 12px 				rgba(0,0,0,.6);
+}
+.ui-shadow {
+	-moz-box-shadow: 0px 1px 4px 			rgba(0,0,0,.3);
+	-webkit-box-shadow: 0px 1px 4px 		rgba(0,0,0,.3);
+	box-shadow: 0px 1px 4px 				rgba(0,0,0,.3);
+}
+.ui-bar-a .ui-shadow,
+.ui-bar-b .ui-shadow ,
+.ui-bar-c .ui-shadow  {
+	-moz-box-shadow: 0px 1px 0 				rgba(255,255,255,.3);
+	-webkit-box-shadow: 0px 1px 0 			rgba(255,255,255,.3);
+	box-shadow: 0px 1px 0 					rgba(255,255,255,.3);
+}
+.ui-shadow-inset {
+	-moz-box-shadow: inset 0px 1px 4px 		rgba(0,0,0,.2);
+	-webkit-box-shadow: inset 0px 1px 4px 	rgba(0,0,0,.2);
+	box-shadow: inset 0px 1px 4px 			rgba(0,0,0,.2);
+}
+.ui-icon-shadow {
+	-moz-box-shadow: 0px 1px 0 				rgba(255,255,255,.4);
+	-webkit-box-shadow: 0px 1px 0 			rgba(255,255,255,.4);
+	box-shadow: 0px 1px 0 					rgba(255,255,255,.4);
+}
+
+
+/* Focus state - set here for specificity
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-focus {
+	-moz-box-shadow: 0px 0px 12px 		#387bbe;
+	-webkit-box-shadow: 0px 0px 12px 	#387bbe;
+	box-shadow: 0px 0px 12px 			#387bbe;
+}
+
+/* unset box shadow in browsers that don't do it right
+-----------------------------------------------------------------------------------------------------------*/
+
+.ui-mobile-nosupport-boxshadow * {
+	-moz-box-shadow: none !important;
+	-webkit-box-shadow: none !important;
+	box-shadow: none !important;
+}
+
+/* ...and bring back focus */
+.ui-mobile-nosupport-boxshadow .ui-focus {
+	outline-width: 2px;
+}/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* Note: Code is in draft form and is subject to change 
+*/
+
+/* some unsets - more probably needed */
+.ui-mobile, .ui-mobile body { height: 100%; }
+.ui-mobile fieldset, .ui-page { padding: 0; margin: 0; }
+.ui-mobile a img, .ui-mobile fieldset { border: 0; }
+
+/* responsive page widths */
+.ui-mobile-viewport {  margin: 0; overflow-x: hidden; -webkit-text-size-adjust: none; -ms-text-size-adjust:none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
+
+/* "page" containers - full-screen views, one should always be in view post-pageload */
+.ui-mobile [data-role=page], .ui-mobile [data-role=dialog], .ui-page { top: 0; left: 0; width: 100%; min-height: 100%; position: absolute; display: none; border: 0; } 
+.ui-mobile .ui-page-active { display: block; overflow: visible; }
+
+/*orientations from js are available */
+.portrait,
+.portrait .ui-page { min-height: 100%; }
+.landscape,
+.landscape .ui-page  { min-height: 100%; }
+
+/* loading screen */
+.ui-loading .ui-mobile-viewport { overflow: hidden !important; }
+.ui-loading .ui-loader { display: block; }
+.ui-loading .ui-page { overflow: hidden;  }
+.ui-loader { display: none; position: absolute; opacity: .85; z-index: 10; left: 50%; width: 200px; margin-left: -130px; margin-top: -35px; padding: 10px 30px; }
+.ui-loader h1 { font-size: 15px; text-align: center; }
+.ui-loader .ui-icon { position: static; display: block; opacity: .9; margin: 0 auto; width: 35px; height: 35px; background-color: transparent; }
+
+/*fouc*/
+.ui-mobile-rendering > * { visibility: hidden; }
+
+/*headers, content panels*/
+.ui-bar, .ui-body { position: relative; padding: .4em 15px;  overflow: hidden; display: block;  clear:both;  }
+.ui-bar { font-size: 16px; margin: 0; }
+.ui-bar h1, .ui-bar h2, .ui-bar h3, .ui-bar h4, .ui-bar h5, .ui-bar h6 { margin: 0; padding: 0; font-size: 16px; display: inline-block; }
+
+.ui-header, .ui-footer { display: block; }
+.ui-page .ui-header, .ui-page .ui-footer { position: relative; }
+.ui-header .ui-btn-left { position: absolute; left: 10px; top: .4em;  }
+.ui-header .ui-btn-right { position: absolute; right: 10px; top: .4em; }
+.ui-header .ui-title, .ui-footer .ui-title { text-align: center; font-size: 16px; display: block; margin: .6em 90px .8em;  padding: 0;  text-overflow: ellipsis; overflow: hidden; white-space: nowrap; outline: 0 !important; }
+
+/*content area*/
+.ui-content { border-width: 0; overflow: visible; overflow-x: hidden; padding: 15px; }
+.ui-page-fullscreen .ui-content { padding:0; }
+
+/* icons sizing */
+.ui-icon { width: 18px; height: 18px; }
+
+/* fullscreen class on ui-content div */
+.ui-fullscreen {  }
+.ui-fullscreen img { max-width: 100%; }
+
+/* non-js content hiding */
+.ui-nojs { position: absolute; left: -9999px; }
+/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.spin  {
+	-webkit-transform: rotate(360deg);
+	-webkit-animation-name: spin;
+	-webkit-animation-duration: 1s;
+	-webkit-animation-iteration-count:  infinite;
+}
+@-webkit-keyframes spin {
+	from {-webkit-transform: rotate(0deg);}
+  	to {-webkit-transform: rotate(360deg);}
+}
+
+/* Transitions from jQtouch (with small modifications): http://www.jqtouch.com/
+Built by David Kaneda and maintained by Jonathan Stark.
+*/
+.in, .out {
+	-webkit-animation-timing-function: ease-in-out;
+	-webkit-animation-duration: 350ms;
+}
+
+.slide.in {
+	-webkit-transform: translateX(0);
+	-webkit-animation-name: slideinfromright;
+}
+
+.slide.out {
+	-webkit-transform: translateX(-100%);
+	-webkit-animation-name: slideouttoleft;
+}
+
+.slide.in.reverse {
+	-webkit-transform: translateX(0);
+	-webkit-animation-name: slideinfromleft;
+}
+
+.slide.out.reverse {
+	-webkit-transform: translateX(100%);
+	-webkit-animation-name: slideouttoright;
+}
+
+.slideup.in {
+	-webkit-transform: translateY(0);
+	-webkit-animation-name: slideinfrombottom;
+	z-index: 10;
+}
+
+.slideup.out {
+	-webkit-animation-name: dontmove;
+	z-index: 0;
+}
+
+.slideup.out.reverse {
+	-webkit-transform: translateY(100%);
+	z-index: 10;
+	-webkit-animation-name: slideouttobottom;
+}
+
+.slideup.in.reverse {
+	z-index: 0;
+	-webkit-animation-name: dontmove;
+}
+.slidedown.in {
+	-webkit-transform: translateY(0);
+	-webkit-animation-name: slideinfromtop;
+	z-index: 10;
+}
+
+.slidedown.out {
+	-webkit-animation-name: dontmove;
+	z-index: 0;
+}
+
+.slidedown.out.reverse {
+	-webkit-transform: translateY(-100%);
+	z-index: 10;
+	-webkit-animation-name: slideouttotop;
+}
+
+.slidedown.in.reverse {
+	z-index: 0;
+	-webkit-animation-name: dontmove;
+}
+
+@-webkit-keyframes slideinfromright {
+    from { -webkit-transform: translateX(100%); }
+    to { -webkit-transform: translateX(0); }
+}
+
+@-webkit-keyframes slideinfromleft {
+    from { -webkit-transform: translateX(-100%); }
+    to { -webkit-transform: translateX(0); }
+}
+
+@-webkit-keyframes slideouttoleft {
+    from { -webkit-transform: translateX(0); }
+    to { -webkit-transform: translateX(-100%); }
+}
+
+@-webkit-keyframes slideouttoright {
+    from { -webkit-transform: translateX(0); }
+    to { -webkit-transform: translateX(100%); }
+}
+
+
+@-webkit-keyframes slideinfromtop {
+    from { -webkit-transform: translateY(-100%); }
+    to { -webkit-transform: translateY(0); }
+}
+
+@-webkit-keyframes slideinfrombottom {
+    from { -webkit-transform: translateY(100%); }
+    to { -webkit-transform: translateY(0); }
+}
+
+@-webkit-keyframes slideouttobottom {
+    from { -webkit-transform: translateY(0); }
+    to { -webkit-transform: translateY(100%); }
+}
+
+@-webkit-keyframes slideouttotop {
+    from { -webkit-transform: translateY(0); }
+    to { -webkit-transform: translateY(-100%); }
+}
+@-webkit-keyframes fadein {
+    from { opacity: 0; }
+    to { opacity: 1; }
+}
+
+@-webkit-keyframes fadeout {
+    from { opacity: 1; }
+    to { opacity: 0; }
+}
+
+.fade.in {
+	opacity: 1;
+	z-index: 10;
+	-webkit-animation-name: fadein;
+}
+.fade.out {
+	z-index: 0;
+	-webkit-animation-name: fadeout;
+}
+
+/* The properties in this body rule are only necessary for the 'flip' transition.
+ * We need specify the perspective to create a projection matrix. This will add
+ * some depth as the element flips. The depth number represents the distance of
+ * the viewer from the z-plane. According to the CSS3 spec, 1000 is a moderate
+ * value.
+ */
+.ui-mobile-viewport-perspective {
+	-webkit-perspective: 1000;
+	position: absolute;
+}
+
+.ui-mobile-viewport-transitioning,
+.ui-mobile-viewport-transitioning .ui-page {
+	width: 100%;
+	height: 100%;
+	overflow: hidden;
+}
+
+.flip {
+	-webkit-animation-duration: .65s;
+	-webkit-backface-visibility:hidden;
+	-webkit-transform:translateX(0); /* Needed to work around an iOS 3.1 bug that causes listview thumbs to disappear when -webkit-visibility:hidden is used. */
+}
+
+.flip.in {
+	-webkit-transform: rotateY(0) scale(1);
+	-webkit-animation-name: flipinfromleft;
+}
+
+.flip.out {
+	-webkit-transform: rotateY(-180deg) scale(.8);
+	-webkit-animation-name: flipouttoleft;
+}
+
+/* Shake it all about */
+
+.flip.in.reverse {
+	-webkit-transform: rotateY(0) scale(1);
+	-webkit-animation-name: flipinfromright;
+}
+
+.flip.out.reverse {
+	-webkit-transform: rotateY(180deg) scale(.8);
+	-webkit-animation-name: flipouttoright;
+}
+
+@-webkit-keyframes flipinfromright {
+    from { -webkit-transform: rotateY(-180deg) scale(.8); }
+    to { -webkit-transform: rotateY(0) scale(1); }
+}
+
+@-webkit-keyframes flipinfromleft {
+    from { -webkit-transform: rotateY(180deg) scale(.8); }
+    to { -webkit-transform: rotateY(0) scale(1); }
+}
+
+@-webkit-keyframes flipouttoleft {
+    from { -webkit-transform: rotateY(0) scale(1); }
+    to { -webkit-transform: rotateY(-180deg) scale(.8); }
+}
+
+@-webkit-keyframes flipouttoright {
+    from { -webkit-transform: rotateY(0) scale(1); }
+    to { -webkit-transform: rotateY(180deg) scale(.8); }
+}
+
+
+/* Hackish, but reliable. */
+
+@-webkit-keyframes dontmove {
+    from { opacity: 1; }
+    to { opacity: 1; }
+}
+
+.pop {
+	-webkit-transform-origin: 50% 50%;
+}
+
+.pop.in {
+	-webkit-transform: scale(1);
+    opacity: 1;
+	-webkit-animation-name: popin;
+	z-index: 10;
+}
+
+.pop.out.reverse {
+	-webkit-transform: scale(.2);
+	opacity: 0;
+	-webkit-animation-name: popout;
+	z-index: 10;
+}
+
+.pop.in.reverse {
+	z-index: 0;
+	-webkit-animation-name: dontmove;
+}
+
+@-webkit-keyframes popin {
+    from {
+        -webkit-transform: scale(.2);
+        opacity: 0;
+    }
+    to {
+        -webkit-transform: scale(1);
+        opacity: 1;
+    }
+}
+
+@-webkit-keyframes popout {
+    from {
+        -webkit-transform: scale(1);
+        opacity: 1;
+    }
+    to {
+        -webkit-transform: scale(.2);
+        opacity: 0;
+    }
+}/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+
+/* content configurations. */
+.ui-grid-a, .ui-grid-b, .ui-grid-c, .ui-grid-d { overflow: hidden; }
+.ui-block-a, .ui-block-b, .ui-block-c, .ui-block-d, .ui-block-e { margin: 0; padding: 0; border: 0; float: left; min-height:1px;}
+
+/* grid solo: 100 - single item fallback */
+.ui-grid-solo .ui-block-a { width: 100%; float: none; }
+
+/* grid a: 50/50 */
+.ui-grid-a .ui-block-a, .ui-grid-a .ui-block-b { width: 50%; }
+.ui-grid-a .ui-block-a { clear: left; }
+
+/* grid b: 33/33/33 */
+.ui-grid-b .ui-block-a, .ui-grid-b .ui-block-b, .ui-grid-b .ui-block-c { width: 33.333%; }
+.ui-grid-b .ui-block-a { clear: left; }
+
+/* grid c: 25/25/25/25 */
+.ui-grid-c .ui-block-a, .ui-grid-c .ui-block-b, .ui-grid-c .ui-block-c, .ui-grid-c .ui-block-d { width: 25%; }
+.ui-grid-c .ui-block-a { clear: left; }
+
+/* grid d: 20/20/20/20/20 */
+.ui-grid-d .ui-block-a, .ui-grid-d .ui-block-b, .ui-grid-d .ui-block-c, .ui-grid-d .ui-block-d, .ui-grid-d .ui-block-e { width: 20%; }
+.ui-grid-d .ui-block-a { clear: left; }
+/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+/* fixed page header & footer configuration */
+.ui-header, .ui-footer, .ui-page-fullscreen .ui-header, .ui-page-fullscreen .ui-footer  { position: absolute;  overflow: hidden; width: 100%; border-left-width: 0; border-right-width: 0; }
+.ui-header-fixed, .ui-footer-fixed {
+	z-index: 1000;
+	-webkit-transform: translateZ(0); /* Force header/footer rendering to go through the same rendering pipeline as native page scrolling. */
+}
+.ui-footer-duplicate, .ui-page-fullscreen .ui-fixed-inline { display: none; }
+.ui-page-fullscreen .ui-header, .ui-page-fullscreen .ui-footer { opacity: .9; }
+/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-navbar { overflow: hidden;  }
+.ui-navbar ul, .ui-navbar-expanded ul { list-style:none; padding: 0; margin: 0; position: relative; display: block; border: 0;}
+.ui-navbar-collapsed ul { float: left; width: 75%; margin-right: -2px; }
+.ui-navbar-collapsed .ui-navbar-toggle { float: left; width: 25%; }
+.ui-navbar li.ui-navbar-truncate { position: absolute; left: -9999px; top: -9999px; }
+.ui-navbar li .ui-btn, .ui-navbar .ui-navbar-toggle .ui-btn { display: block; font-size: 12px; text-align: center; margin: 0; border-right-width: 0; }
+.ui-navbar li .ui-btn {  margin-right: -1px; }
+.ui-navbar li .ui-btn:last-child { margin-right: 0; }
+.ui-header .ui-navbar li .ui-btn, .ui-header .ui-navbar .ui-navbar-toggle .ui-btn,
+.ui-footer .ui-navbar li .ui-btn, .ui-footer .ui-navbar .ui-navbar-toggle .ui-btn { border-top-width: 0; border-bottom-width: 0; }
+.ui-navbar .ui-btn-inner { padding-left: 2px; padding-right: 2px; }
+.ui-navbar-noicons li .ui-btn .ui-btn-inner, .ui-navbar-noicons .ui-navbar-toggle .ui-btn-inner { padding-top: .8em; padding-bottom: .9em; }
+/*expanded page styles*/
+.ui-navbar-expanded .ui-btn { margin: 0; font-size: 14px; }
+.ui-navbar-expanded .ui-btn-inner { padding-left: 5px; padding-right: 5px;  }
+.ui-navbar-expanded .ui-btn-icon-top .ui-btn-inner { padding: 45px 5px 15px; text-align: center; }
+.ui-navbar-expanded .ui-btn-icon-top .ui-icon { top: 15px; }
+.ui-navbar-expanded .ui-btn-icon-bottom .ui-btn-inner { padding: 15px 5px 45px; text-align: center; }
+.ui-navbar-expanded .ui-btn-icon-bottom .ui-icon { bottom: 15px; }
+.ui-navbar-expanded li .ui-btn .ui-btn-inner { min-height: 2.5em; }
+.ui-navbar-expanded .ui-navbar-noicons .ui-btn .ui-btn-inner { padding-top: 1.8em; padding-bottom: 1.9em; }
+/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-btn { display: block; text-align: center; cursor:pointer;  position: relative; margin: .5em 5px; padding: 0; }
+.ui-btn:focus, .ui-btn:active { outline: none; }
+.ui-header .ui-btn, .ui-footer .ui-btn, .ui-bar .ui-btn { display: inline-block; font-size: 13px; margin: 0; }
+.ui-btn-inline { display: inline-block; }
+.ui-btn-inner { padding: .6em 25px; display: block; height: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; position: relative; }
+.ui-header .ui-btn-inner, .ui-footer .ui-btn-inner, .ui-bar .ui-btn-inner { padding: .4em 8px .5em; }
+.ui-btn-icon-notext { display: inline-block; width: 20px; height: 20px; padding: 2px 1px 2px 3px; text-indent: -9999px; }
+.ui-btn-icon-notext .ui-btn-inner { padding: 0; }
+.ui-btn-icon-notext .ui-btn-text { position: absolute; left: -999px; }
+.ui-btn-icon-left .ui-btn-inner { padding-left: 33px; }
+.ui-header .ui-btn-icon-left .ui-btn-inner,
+.ui-footer .ui-btn-icon-left .ui-btn-inner,
+.ui-bar .ui-btn-icon-left .ui-btn-inner { padding-left: 27px; }
+.ui-btn-icon-right .ui-btn-inner { padding-right: 33px; }
+.ui-header .ui-btn-icon-right .ui-btn-inner,
+.ui-footer .ui-btn-icon-right .ui-btn-inner,
+.ui-bar .ui-btn-icon-right .ui-btn-inner { padding-right: 27px; }
+.ui-btn-icon-top .ui-btn-inner { padding-top: 33px; }
+.ui-header .ui-btn-icon-top .ui-btn-inner,
+.ui-footer .ui-btn-icon-top .ui-btn-inner,
+.ui-bar .ui-btn-icon-top .ui-btn-inner { padding-top: 27px; }
+.ui-btn-icon-bottom .ui-btn-inner { padding-bottom: 33px; }
+.ui-header .ui-btn-icon-bottom .ui-btn-inner,
+.ui-footer .ui-btn-icon-bottom .ui-btn-inner,
+.ui-bar .ui-btn-icon-bottom .ui-btn-inner { padding-bottom: 27px; }
+
+/*btn icon positioning*/
+.ui-btn-icon-notext .ui-icon { display: block; }
+.ui-btn-icon-left .ui-icon, .ui-btn-icon-right .ui-icon { position: absolute; top: 50%; margin-top: -9px; }
+.ui-btn-icon-top .ui-icon, .ui-btn-icon-bottom .ui-icon { position: absolute; left: 50%;  margin-left: -9px; }
+.ui-btn-icon-left .ui-icon { left: 10px; }
+.ui-btn-icon-right .ui-icon {right: 10px; }
+.ui-header .ui-btn-icon-left .ui-icon,
+.ui-footer .ui-btn-icon-left .ui-icon,
+.ui-bar .ui-btn-icon-left .ui-icon { left: 4px; }
+.ui-header .ui-btn-icon-right .ui-icon,
+.ui-footer .ui-btn-icon-right .ui-icon,
+.ui-bar .ui-btn-icon-right .ui-icon { right: 4px; }
+.ui-header .ui-btn-icon-top .ui-icon,
+.ui-footer .ui-btn-icon-top .ui-icon,
+.ui-bar .ui-btn-icon-top .ui-icon { top: 4px; }
+.ui-header .ui-btn-icon-bottom .ui-icon,
+.ui-footer .ui-btn-icon-bottom .ui-icon,
+.ui-bar .ui-btn-icon-bottom .ui-icon { bottom: 4px; }
+.ui-btn-icon-top .ui-icon { top: 5px; }
+.ui-btn-icon-bottom .ui-icon { bottom: 5px; }
+/*hiding native button,inputs */
+.ui-btn-hidden {  position: absolute; top: 0; left: 0; width: 100%; height: 100%; -webkit-appearance: button; opacity: 0; cursor: pointer; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter: alpha(opacity=0); }
+/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-collapsible-contain { margin: .5em 0; }
+.ui-collapsible-heading { font-size: 16px; display: block; margin: 0 -8px; padding: 0; border-width: 0 0 1px 0; position: relative; }
+.ui-collapsible-heading a { text-align: left; margin: 0;  }
+.ui-collapsible-heading a .ui-btn-inner { padding-left: 40px; }
+.ui-collapsible-heading a span.ui-btn { position: absolute; left: 6px; top: 50%; margin: -12px 0 0 0; width: 20px; height: 20px; padding: 1px 0px 1px 2px; text-indent: -9999px; }
+.ui-collapsible-heading a span.ui-btn .ui-btn-inner { padding: 0; }
+.ui-collapsible-heading a span.ui-btn .ui-icon { left: 0; margin-top: -10px; }
+.ui-collapsible-heading-status { position:absolute; left:-9999px; }
+.ui-collapsible-content {  display: block; padding: 10px 0 10px 8px; }
+.ui-collapsible-content-collapsed { display: none; }
+
+.ui-collapsible-set { margin: .5em 0; }
+.ui-collapsible-set .ui-collapsible-contain { margin: -1px 0 0; }
+/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-controlgroup, fieldset.ui-controlgroup { padding: 0; margin: .5em 0 1em; }
+.ui-bar .ui-controlgroup { margin: 0 .3em; }
+.ui-controlgroup-label { font-size: 16px; line-height: 1.4; font-weight: normal; margin: 0 0 .3em; }
+.ui-controlgroup-controls { display: block; width: 95%;}
+.ui-controlgroup li { list-style: none; }
+.ui-controlgroup-vertical .ui-btn,
+.ui-controlgroup-vertical .ui-checkbox, .ui-controlgroup-vertical .ui-radio { margin: 0; border-bottom-width: 0;  }
+.ui-controlgroup-vertical .ui-controlgroup-last { border-bottom-width: 1px; }
+.ui-controlgroup-horizontal { padding: 0; }
+.ui-controlgroup-horizontal .ui-btn,
+.ui-controlgroup-horizontal .ui-checkbox, .ui-controlgroup-horizontal .ui-radio { display: inline-block; margin: 0 -5px 0 0; }
+.ui-controlgroup-horizontal .ui-checkbox, .ui-controlgroup-horizontal .ui-radio { display: inline; }
+.ui-controlgroup-horizontal .ui-checkbox .ui-btn, .ui-controlgroup-horizontal .ui-radio .ui-btn,
+.ui-controlgroup-horizontal .ui-checkbox:last-child, .ui-controlgroup-horizontal .ui-radio:last-child { margin-right: 0; }
+.ui-controlgroup-horizontal .ui-controlgroup-last { margin-right: 0; }
+.ui-controlgroup .ui-checkbox label, .ui-controlgroup .ui-radio label { font-size: 16px;  }
+/* conflicts with listview..
+.ui-controlgroup .ui-btn-icon-notext { width: 30px; height: 30px; text-indent: -9999px; }
+.ui-controlgroup .ui-btn-icon-notext .ui-btn-inner {  padding: 5px 6px 5px 5px; }
+*/
+
+.min-width-480px .ui-controlgroup-label { vertical-align: top; display: inline-block;  width: 20%;  margin: 0 2% 0 0;  }
+.min-width-480px .ui-controlgroup-controls { width: 60%; display: inline-block; } /*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-dialog { min-height: 480px; }
+.ui-dialog .ui-header, .ui-dialog .ui-content,  .ui-dialog .ui-footer { margin: 15px; position: relative; }
+.ui-dialog .ui-header, .ui-dialog .ui-footer { z-index: 10; width: auto; }
+.ui-dialog .ui-content, .ui-dialog .ui-footer { margin-top: -15px;  }/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-checkbox, .ui-radio { position:relative;  margin: .2em 0 .5em; z-index: 1;  }
+.ui-checkbox .ui-btn, .ui-radio .ui-btn { margin: 0; text-align: left; z-index: 2; }
+.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner { padding-left: 45px; }
+.ui-checkbox .ui-btn-icon-right .ui-btn-inner, .ui-radio .ui-btn-icon-right .ui-btn-inner { padding-right: 45px; }
+.ui-checkbox .ui-btn-icon-left .ui-icon, .ui-radio .ui-btn-icon-left .ui-icon {left: 15px; }
+.ui-checkbox .ui-btn-icon-right .ui-icon, .ui-radio .ui-btn-icon-right .ui-icon {right: 15px; }
+/* input, label positioning */
+.ui-checkbox input,.ui-radio input { position:absolute; left:20px; top:50%; width: 10px; height: 10px;  margin:-5px 0 0 0; outline: 0 !important; z-index: 1; }/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-field-contain { background: none; padding: 1.5em 0; margin: 0; border-bottom-width: 1px; overflow: visible; }
+.ui-field-contain:first-child { border-top-width: 0; }
+.min-width-480px .ui-field-contain { border-width: 0; padding: 0; margin: 1em 0; }/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-select { display: block; position: relative; }
+.ui-select select { position: absolute; left: -9999px; top: -9999px; }
+.ui-select .ui-btn { overflow: hidden; }
+.ui-select .ui-btn select { cursor: pointer; -webkit-appearance: button; left: 0; top:0; width: 100%; height: 100%; opacity: 0; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter: alpha(opacity=0); }
+.ui-select .ui-btn select.ui-select-nativeonly { opacity: 1; }
+
+.ui-select .ui-btn-icon-right .ui-btn-inner { padding-right: 45px; } 
+.ui-select .ui-btn-icon-right .ui-icon { right: 15px;  }
+
+/* labels */
+label.ui-select { font-size: 16px; line-height: 1.4;  font-weight: normal; margin: 0 0 .3em; display: block; }
+
+/*listbox*/
+.ui-select .ui-btn-text, .ui-selectmenu .ui-btn-text { display: inline-block; min-height: 1em; }
+.ui-select .ui-btn-text { text-overflow: ellipsis; overflow: hidden; display: block;}
+
+.ui-selectmenu { position: absolute; padding: 0; z-index: 100 !important; width: 80%; max-width: 350px; padding: 6px; }
+.ui-selectmenu .ui-listview { margin: 0; }
+.ui-selectmenu .ui-btn.ui-li-divider { cursor: default; }
+.ui-selectmenu-hidden { top: -9999px; left: -9999px; }
+.ui-selectmenu-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%;  z-index: 99; }
+.ui-screen-hidden, .ui-selectmenu-list .ui-li .ui-icon { display: none; }
+.ui-selectmenu-list .ui-li .ui-icon { display: block; }
+.ui-li.ui-selectmenu-placeholder { display: none; }
+.ui-selectmenu .ui-header .ui-title { margin: 0.6em 46px 0.8em; }
+
+.min-width-480px label.ui-select { display: inline-block;  width: 20%;  margin: 0 2% 0 0; }
+.min-width-480px .ui-select { width: 60%; display: inline-block; }
+
+/* when no placeholder is defined in a multiple select, the header height doesn't even extend past the close button.  this shim's content in there */
+.ui-selectmenu .ui-header h1:after { content: '.'; visibility: hidden; }/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+label.ui-input-text { font-size: 16px; line-height: 1.4; display: block; font-weight: normal; margin: 0 0 .3em; }
+input.ui-input-text, textarea.ui-input-text { background-image: none; padding: .4em; line-height: 1.4; font-size: 16px; display: block; width: 95%; }
+input.ui-input-text { -webkit-appearance: none; }
+textarea.ui-input-text { height: 50px; -webkit-transition: height 200ms linear; -moz-transition: height 200ms linear; -o-transition: height 200ms linear; transition: height 200ms linear; }
+.ui-input-search { padding: 0 30px; width: 77%; background-position: 8px 50%; background-repeat: no-repeat; position: relative; }
+.ui-input-search input.ui-input-text { border: none; width: 98%; padding: .4em 0; margin: 0; display: block; background: transparent none; outline: 0 !important; }
+.ui-input-search .ui-input-clear { position: absolute; right: 0; top: 50%; margin-top: -14px; }
+.ui-input-search .ui-input-clear-hidden { display: none; }
+
+/* orientation adjustments - incomplete!*/
+.min-width-480px label.ui-input-text  { vertical-align: top;   }
+.min-width-480px label.ui-input-text { display: inline-block;  width: 20%;  margin: 0 2% 0 0; }
+.min-width-480px input.ui-input-text, 
+.min-width-480px textarea.ui-input-text, 
+.min-width-480px .ui-input-search { width: 60%; display: inline-block; } 
+.min-width-480px .ui-input-search { width: 50%; }
+.min-width-480px .ui-input-search input.ui-input-text { width: 98%; /*echos rule from above*/ }
+/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+.ui-listview { margin: 0; counter-reset: listnumbering; }
+.ui-content .ui-listview { margin: -15px; }
+.ui-content .ui-listview-inset { margin: 1em 0;  }
+.ui-listview, .ui-li { list-style:none; padding:0; }
+.ui-li, .ui-li.ui-field-contain { display: block; margin:0; position: relative; overflow: visible; text-align: left; border-width: 0; border-top-width: 1px; }
+.ui-li .ui-btn-text a.ui-link-inherit { text-overflow: ellipsis; overflow: hidden; white-space: nowrap;  }
+.ui-li-divider, .ui-li-static { padding: .5em 15px; font-size: 14px; font-weight: bold;  }
+.ui-li-divider { counter-reset: listnumbering;  }
+ol.ui-listview .ui-link-inherit:before, ol.ui-listview .ui-li-static:before, .ui-li-dec { font-size: .8em; display: inline-block; padding-right: .3em; font-weight: normal;counter-increment: listnumbering; content: counter(listnumbering) ". "; }
+ol.ui-listview .ui-li-jsnumbering:before { content: "" !important; } /* to avoid chance of duplication */
+.ui-listview-inset .ui-li { border-right-width: 1px; border-left-width: 1px; }
+.ui-li:last-child, .ui-li.ui-field-contain:last-child { border-bottom-width: 1px; }
+.ui-li>.ui-btn-inner { display: block; position: relative; padding: 0; }
+.ui-li .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li { padding: .7em 75px .7em 15px; display: block; }
+.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li-has-thumb  { min-height: 60px; padding-left: 100px; }
+.ui-li-has-icon .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li-has-icon {  min-height: 20px; padding-left: 40px; }
+.ui-li-heading { font-size: 16px; font-weight: bold; display: block; margin: .6em 0; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;  }
+.ui-li-desc {  font-size: 12px; font-weight: normal; display: block; margin: -.5em 0 .6em; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
+.ui-li-thumb, .ui-li-icon { position: absolute; left: 1px; top: 0; max-height: 80px; max-width: 80px; }
+.ui-li-icon { max-height: 40px; max-width: 40px; left: 10px; top: .9em; }
+.ui-li-thumb, .ui-li-icon, .ui-li-content { float: left; margin-right: 10px; }
+
+.ui-li-aside { float: right; width: 50%; text-align: right; margin: .3em 0; }
+.min-width-480px .ui-li-aside { width: 45%; }
+.ui-li-divider { cursor: default; }
+.ui-li-has-alt .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li-has-alt { padding-right: 95px; }
+.ui-li-count { position: absolute; font-size: 11px; font-weight: bold; padding: .2em .5em; top: 50%; margin-top: -.9em; right: 38px; }
+.ui-li-divider .ui-li-count, .ui-li-static .ui-li-count { right: 10px; }
+.ui-li-has-alt .ui-li-count { right: 55px; }
+.ui-li-link-alt { position: absolute; width: 40px; height: 100%; border-width: 0; border-left-width: 1px; top: 0; right: 0; margin: 0; padding: 0; }
+.ui-li-link-alt .ui-btn { overflow: hidden; position: absolute; right: 8px; top: 50%; margin: -11px 0 0 0; border-bottom-width: 1px; }
+.ui-li-link-alt .ui-btn-inner { padding: 0; position: static; }
+.ui-li-link-alt .ui-btn .ui-icon { right: 50%; margin-right: -9px;  }
+
+.ui-listview-filter { border-width: 0; overflow: hidden; margin: -15px -15px 15px -15px }
+.ui-listview-filter .ui-input-search { margin: 5px; width: auto; display: block; }
+
+.ui-listview-filter-inset { margin: -15px -5px -15px -5px; background: transparent; }
+
+/* Odd iPad positioning issue. */
+@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
+    .ui-li .ui-btn-text { overflow:  visible; }
+}/*
+* jQuery Mobile Framework
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
+*/
+label.ui-slider { display: block; }
+input.ui-slider-input, .min-width-480px input.ui-slider-input { display: inline-block; width: 50px; }
+select.ui-slider-switch { display: none; }
+div.ui-slider { position: relative; display: inline-block; overflow: visible; height: 15px; padding: 0; margin: 0 2% 0 20px; top: 4px; width: 66%; }
+a.ui-slider-handle { position: absolute; z-index: 10;  top: 50%; width: 28px; height: 28px; margin-top: -15px; margin-left: -15px; }
+a.ui-slider-handle .ui-btn-inner { padding-left: 0; padding-right: 0; }
+.min-width-480px label.ui-slider { display: inline-block;  width: 20%;  margin: 0 2% 0 0; }
+.min-width-480px div.ui-slider { width: 45%; }
+
+div.ui-slider-switch { height: 32px;  overflow: hidden; margin-left: 0; }
+div.ui-slider-inneroffset { margin-left: 50%; position: absolute; top: 1px; height: 100%; width: 50%; }
+div.ui-slider-handle-snapping { -webkit-transition: left 100ms linear; }
+div.ui-slider-labelbg { position: absolute; top:0; margin: 0; border-width: 0; }
+div.ui-slider-switch div.ui-slider-labelbg-a { width: 60%; height: 100%; left: 0; }
+div.ui-slider-switch div.ui-slider-labelbg-b { width: 60%; height: 100%; right: 0; }
+.ui-slider-switch-a div.ui-slider-labelbg-a, .ui-slider-switch-b div.ui-slider-labelbg-b { z-index: -1; }
+.ui-slider-switch-a div.ui-slider-labelbg-b, .ui-slider-switch-b div.ui-slider-labelbg-a { z-index: 0; }
+
+div.ui-slider-switch a.ui-slider-handle { z-index: 20;  width: 101%; height: 32px; margin-top: -18px; margin-left: -101%; }
+span.ui-slider-label { width: 100%; position: absolute;height: 32px;  font-size: 16px; text-align: center; line-height: 2; background: none; border-color: transparent; }
+span.ui-slider-label-a { left: -100%;  margin-right: -1px }
+span.ui-slider-label-b { right: -100%;  margin-left: -1px }
+

--- /dev/null
+++ b/dotcloud/postinstall
@@ -1,1 +1,19 @@
+#!/bin/bash
+#dotcloud postinstall
 
+curl http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip \
+-o /home/dotcloud/current/cbrfeed.zip
+wget http://s3-ap-southeast-1.amazonaws.com/busresources/Graph.obj \
+-O /tmp/Graph.obj
+
+#db setup
+#curl https://github.com/maxious/ACTBus-ui/raw/master/transitdata.cbrfeed.sql.gz -o transitdata.cbrfeed.sql.gz
+#curl https://github.com/maxious/ACTBus-ui/raw/master/lib/postgis.sql -o postgis.sql
+#createlang -d transitdata plpgsql
+#psql -d transitdata -f postgis.sql
+#gunzip /var/www/transitdata.cbrfeed.sql.gz
+#psql -d transitdata -f transitdata.cbrfeed.sql
+#createuser transitdata -SDRP
+#password transitdata
+#psql -c \"GRANT SELECT ON TABLE agency,calendar,calendar_dates,routes,stop_times,stops,trips TO transitdata;\"
+

file:b/dotcloud/push.sh (new)
--- /dev/null
+++ b/dotcloud/push.sh
@@ -1,1 +1,7 @@
+#wget http://s3-ap-southeast-1.amazonaws.com/busresources/opentripplanner-webapp.war 
+cp ~/workspace/opentripplanner/maven.1277125291275/opentripplanner-webapp/target/opentripplanner-webapp.war ./
+#wget http://s3-ap-southeast-1.amazonaws.com/busresources/opentripplanner-api-webapp.war 
+cp ~/workspace/opentripplanner/maven.1277125291275/opentripplanner-api-webapp/target/opentripplanner-api-webapp.war ./
 
+dotcloud push actbus.otp ./
+

--- a/feedback.php
+++ b/feedback.php
@@ -24,7 +24,10 @@
 		mail($address, $topic, $message);
 	}
 }
-
+if (isset($_REQUEST['feedback']) || isset($_REQUEST['newlocation'])){
+	sendEmail("bus.lambda feedback",print_r($_REQUEST,true));
+	echo "<h2 style='text-align: center;'>Thank you for your feedback!</h2>";
+} else {
 $stopid = "";
 $stopcode = "";
 $urlparts = explode("?",$_SERVER["HTTP_REFERER"]);
@@ -39,32 +42,40 @@
 
 ?>
 <h3>Add/Move/Delete a Bus Stop Location</h3>
+<form action="feedback.php" method="post">
 StopID: <input type="text" name="stopid" value="<?php echo $stopid ?>"/><br>
 or StopCode:  <input type="text" name="stopcode" value="<?php echo $stopcode ?>"/><br>
 <small> if you click on feedback from a stop page, these will get filled in automatically. else describe the location/street of the stop in one of these boxes </small><br>
 
 Suggested Stop Location (lat/long or words):  <input type="text" name="newlocation"/><br>
-<small> if your device supports javascript, you can pick a location from the map above</small><br>
+<!--<small> if your device supports javascript, you can pick a location from the map above</small><br>-->
 
 <input type="submit" value="Submit!"/>
-
+</form>
 <h3>Bug Report/Feedback</h3>
 Please leave feedback about bugs/errors or general suggestions about improvements that could be made to the way the data is presented!
-<textarea id="feedback">
+<form action="feedback.php" method="post">
+<textarea name="feedback">
 </textarea>
-<textarea id="extrainfo">
+<textarea name="extrainfo" id="extrainfo">
 <?php
   echo "Referrer URL: ".$_SERVER["HTTP_REFERER"];
+  echo "\nCurrent page URL: ".curPageURL();
   echo "\nUser Agent: ".$_SERVER["HTTP_USER_AGENT"];
   echo "\nUser host/IP: ".$_SERVER["HTTP_X_FORWARDED_FOR"]." ".$_SERVER["REMOTE_ADDR"]; 
   echo "\nServer host/IP: ".php_uname("n");
   echo "\nCurrent date/time: ". date("c");
   echo "\nCurrent code revision: ".exec("git rev-parse --short HEAD");
-  echo "\nCurrent timetables version: ".@filemtime('cbrfeed.zip');
+  echo "\nCurrent timetables version: ".date("c",@filemtime('cbrfeed.zip'));
   echo "\nDump of session: ".print_r($_SESSION,true);
 ?>
 </textarea>
 
 <input type="submit" value="Submit!"/>
+</form>
+<?php
+}
+include_footer();
+?>
 
 

--- /dev/null
+++ b/include/common-db.inc.php
@@ -1,1 +1,21 @@
+<?php
+  if (php_uname('n') == "actbus-www") {
+    $conn = pg_connect("dbname=transitdata user=transitdata password=transitdata host=bus-main.lambdacomplex.org");
+  } else if (isDebugServer()) {
+    $conn = pg_connect("dbname=transitdata user=postgres password=snmc");
+  } else {
+    $conn = pg_connect("dbname=transitdata user=transitdata password=transitdata ");
+  }
+  if (!$conn) {
+      die("A database error occurred.\n");
+  }
+  
+  function databaseError($errMsg) {
+    die($errMsg);
+  }
+ 
+  include('db/route-dao.inc.php');
+  include('db/trip-dao.inc.php');
+  include('db/stop-dao.inc.php');  
+  ?>
 

--- a/include/common-geo.inc.php
+++ b/include/common-geo.inc.php
@@ -46,9 +46,9 @@
 		$center = $totalLat / sizeof($mapPoints) . "," . $totalLon / sizeof($mapPoints);
 	}
 	$output = "";
-	if ($collapsible) $output.= '<div data-role="collapsible" data-collapsed="true"><h3>Open Map...</h3>';
-	$output.= '<center><img src="' . curPageURL() . 'lib/staticmaplite/staticmap.php?center=' . $center . '&zoom=' . $zoom . '&size=' . $width . 'x' . $height . '&markers=' . 
-$markers . '" width=' . $width . ' height=' . $height . '></center>';
+	if ($collapsible) $output.= '<div class="map" data-role="collapsible" data-collapsed="true"><h3>Open Map...</h3>';
+	$output.= '<img class="map" src="' . curPageURL() . '/lib/staticmaplite/staticmap.php?center=' . $center . '&amp;zoom=' . $zoom . '&amp;size=' . $width . 'x' . $height . '&amp;markers=' . 
+$markers . '" width=' . $width . ' height=' . $height . '>';
 	if ($collapsible) $output.= '</div>';
 	return $output;
 }
@@ -70,6 +70,7 @@
 	  else return round($km,2)."k";
 	} else return floor($km * 1000);
 }
+
 function decodePolylineToArray($encoded)
 {
 	// source: http://latlongeeks.com/forum/viewtopic.php?f=4&t=5

--- a/include/common-net.inc.php
+++ b/include/common-net.inc.php
@@ -5,7 +5,7 @@
 	$ch = curl_init($url);
 	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 	curl_setopt($ch, CURLOPT_HEADER, 0);
-	curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+	curl_setopt($ch, CURLOPT_TIMEOUT, 45);
 	$page = curl_exec($ch);
 	if (curl_errno($ch)) {
 		echo "<font color=red> Database temporarily unavailable: ";
@@ -24,7 +24,8 @@
 	$isHTTPS = (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on");
 	$port = (isset($_SERVER["SERVER_PORT"]) && ((!$isHTTPS && $_SERVER["SERVER_PORT"] != "80") || ($isHTTPS && $_SERVER["SERVER_PORT"] != "443")));
 	$port = ($port) ? ':' . $_SERVER["SERVER_PORT"] : '';
-	$url = ($isHTTPS ? 'https://' : 'http://') . $_SERVER["SERVER_NAME"] . $port . htmlentities(dirname($_SERVER['PHP_SELF']) , ENT_QUOTES) . "/";
+	$url = ($isHTTPS ? 'https://' : 'http://') . $_SERVER["SERVER_NAME"] . $port . htmlentities(dirname($_SERVER['PHP_SELF']) , ENT_QUOTES);
 	return $url;
 }
 ?>
+

--- a/include/common-session.inc.php
+++ b/include/common-session.inc.php
@@ -9,7 +9,7 @@
 	$_SESSION['time'] = filter_var($_REQUEST['time'], FILTER_SANITIZE_STRING);
 	sessionUpdated();
 }
-if (isset($_REQUEST['geolocate'])) {
+if (isset($_REQUEST['geolocate']) && $_REQUEST['geolocate'] != "Enter co-ordinates or address here") {
 	$geocoded = false;
 	if (isset($_REQUEST['lat']) && isset($_REQUEST['lon'])) {
 		$_SESSION['lat'] = trim(filter_var($_REQUEST['lat'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
@@ -17,7 +17,6 @@
 	}
 	else {
 		$geolocate = filter_var($_REQUEST['geolocate'], FILTER_SANITIZE_URL);
-		echo $_REQUEST['geolocate'];
 		if (startsWith($geolocate, "-")) {
 			$locateparts = explode(",", $geolocate);
 			$_SESSION['lat'] = $locateparts[0];
@@ -52,6 +51,9 @@
 	session_destroy();
 	session_start();
 }
-debug(print_r($_SESSION, true) , "session");
+//debug(print_r($_SESSION, true) , "session");
 
+function current_time() {
+	return ($_SESSION['time']? $_SESSION['time'] : date("H:i:s"));
+}
 ?>

--- a/include/common-template.inc.php
+++ b/include/common-template.inc.php
@@ -1,28 +1,27 @@
 <?php
-  // Copyright 2009 Google Inc. All Rights Reserved.
-  $GA_ACCOUNT = "MO-22173039-1";
-  $GA_PIXEL = "/lib/ga.php";
-
-  function googleAnalyticsGetImageUrl() {
-    global $GA_ACCOUNT, $GA_PIXEL;
-    $url = "";
-    $url .= $GA_PIXEL . "?";
-    $url .= "utmac=" . $GA_ACCOUNT;
-    $url .= "&utmn=" . rand(0, 0x7fffffff);
-    $referer = $_SERVER["HTTP_REFERER"];
-    $query = $_SERVER["QUERY_STRING"];
-    $path = $_SERVER["REQUEST_URI"];
-    if (empty($referer)) {
-      $referer = "-";
-    }
-    $url .= "&utmr=" . urlencode($referer);
-    if (!empty($path)) {
-      $url .= "&utmp=" . urlencode($path);
-    }
-    $url .= "&guid=ON";
-    return str_replace("&", "&amp;", $url);
-  }
-
+// Copyright 2009 Google Inc. All Rights Reserved.
+$GA_ACCOUNT = "MO-22173039-1";
+$GA_PIXEL = "/lib/ga.php";
+function googleAnalyticsGetImageUrl()
+{
+	global $GA_ACCOUNT, $GA_PIXEL;
+	$url = "";
+	$url.= $GA_PIXEL . "?";
+	$url.= "utmac=" . $GA_ACCOUNT;
+	$url.= "&utmn=" . rand(0, 0x7fffffff);
+	$referer = $_SERVER["HTTP_REFERER"];
+	$query = $_SERVER["QUERY_STRING"];
+	$path = $_SERVER["REQUEST_URI"];
+	if (empty($referer)) {
+		$referer = "-";
+	}
+	$url.= "&utmr=" . urlencode($referer);
+	if (!empty($path)) {
+		$url.= "&utmp=" . urlencode($path);
+	}
+	$url.= "&guid=ON";
+	return str_replace("&", "&amp;", $url);
+}
 function include_header($pageTitle, $pageType, $opendiv = true, $geolocate = false, $datepicker = false)
 {
 	echo '
@@ -34,27 +33,34 @@
         <meta name="google-site-verification" 
 content="-53T5Qn4TB_de1NyfR_ZZkEVdUNcNFSaYKSFkWKx-sY" />';
 	if ($datepicker) echo '<link rel="stylesheet"  href="css/jquery.ui.datepicker.mobile.css" />';
-	if (isDebugServer()) echo '<link rel="stylesheet"  href="css/jquery-mobile-1.0a3.css" />
+	if (isDebugServer()) {
+		echo '<link rel="stylesheet"  href="css/jquery.mobile-1.0a4.css" />
+	
          <script type="text/javascript" src="js/jquery-1.5.js"></script>
 	 <script>$(document).bind("mobileinit", function(){
   $.mobile.ajaxEnabled = false;
 });
 </script>
-        <script type="text/javascript" src="js/jquery-mobile-1.0a3.js"></script>';
-	else echo '<link rel="stylesheet"  href="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.css" />
-        <script type="text/javascript" src="http://code.jquery.com/jquery-1.5.1.min.js"></script>
+        <script type="text/javascript" src="js/jquery.mobile-1.0a4.js"></script>';
+	}
+	else {
+		echo '<link rel="stylesheet"  href="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.css" />
+        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
 	 <script>$(document).bind("mobileinit", function(){
   $.mobile.ajaxEnabled = false;
 });
 </script>
-        <script type="text/javascript" src="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.js"></script>';
-	if ($datepicker) echo '<script> 
+        <script type="text/javascript" src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js"></script>';
+	}
+	if ($datepicker) {
+		echo '<script> 
 		//reset type=date inputs to text
 		$( document ).bind( "mobileinit", function(){
 			$.mobile.page.prototype.options.degradeInputs.date = true;
 		});	
 	</script> 
 	<script src="js/jQuery.ui.datepicker.js"></script>';
+	}
 	echo '<style type="text/css">
      .ui-navbar {
      width: 100%;
@@ -68,10 +74,14 @@
     .ui-listview-filter {
         margin: 0 !important;
      }
-     .ui-icon-navigation {
+    .ui-icon-navigation {
         background-image: url(css/images/113-navigation.png);
         background-position: 1px 0;
      }
+    .ui-icon-beaker {
+        background-image: url(css/images/91-beaker-2.png);
+        background-position: 1px 0;
+    }
     #footer {
         text-size: 0.75em;
         text-align: center;
@@ -92,6 +102,14 @@
     #extrainfo {
     visibility: hidden;
     display: none;
+    }
+    #servicewarning {
+    padding: 1em;
+    margin-bottom: 0.5em;
+    text-size: 0.2em;
+    background-color: #FF9;
+    -moz-border-radius: 15px;
+border-radius: 15px;
     }
     // source http://webaim.org/techniques/skipnav/
     #skip a, #skip a:hover, #skip a:visited 
@@ -145,17 +163,17 @@
 $('#here').show();
 });
 ";
-if (!isset($_SESSION['lat']) || $_SESSION['lat'] == "") echo "geolocate();";
-echo "</script> ";
+		if (!isset($_SESSION['lat']) || $_SESSION['lat'] == "") echo "geolocate();";
+		echo "</script> ";
 	}
 	if (isAnalyticsOn()) echo '
-<script type="text/javascript">'."
+<script type="text/javascript">' . "
 
   var _gaq = _gaq || [];
   _gaq.push(['_setAccount', 'UA-22173039-1']);
   _gaq.push(['_trackPageview']);
 </script>";
-echo '</head>
+	echo '</head>
 <body>
     <div id="skip">
     <a href="#maincontent">Skip to content</a>
@@ -163,26 +181,32 @@
  ';
 	if ($opendiv) {
 		echo '<div data-role="page"> 
- <script>
-$(document).ready(function ()
-{
-    document.title = "' . $pageTitle . '";
-});
-</script>
 	<div data-role="header" data-position="inline">
-	<a href="'.$_SERVER["HTTP_REFERER"].'" data-icon="arrow-l" data-rel="back">Back</a> 
+	<a href="' . (isset($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : "javascript:history.go(-1)") . '" data-icon="arrow-l" data-rel="back" class="ui-btn-left">Back</a> 
 		<h1>' . $pageTitle . '</h1>
+		<a href="/index.php" data-icon="home" class="ui-btn-right">Home</a>
 	</div><!-- /header -->
         <a name="maincontent" id="maincontent"></a>
         <div data-role="content"> ';
-	}
+		$overrides = getServiceOverride();
+		if ($overrides['service_id']) {
+				if ($overrides['service_id'] == "noservice") {
+					echo '<div id="servicewarning">Buses are <strong>not running today</strong> due to industrial action/public holiday. See <a 
+href="http://www.action.act.gov.au">http://www.action.act.gov.au</a> for details.</div>';
+				}
+				else {
+					echo '<div id="servicewarning">Buses are running on an altered timetable today due to industrial action/public holiday. See <a href="http://www.action.act.gov.au">http://www.action.act.gov.au</a> for details.</div>';
+				}
+			}
+		}
+
 }
 function include_footer()
 {
-	echo '<div id="footer"><a href="about.php">About/Contact Us</a>&nbsp;<a href="feedback.php">Feedback/Bug Report</a></a>';
+	echo '<div id="footer"><a href="about.php">About/Contact Us</a>&nbsp;<a href="feedback.php">Feedback/Bug Report</a>';
 	echo '</div>';
-        if (isAnalyticsOn()) {
-	echo "<script>  (function() {
+	if (isAnalyticsOn()) {
+		echo "<script>  (function() {
     var ga = document.createElement('script'); ga.type = 
 'text/javascript'; ga.async = true;
     ga.src = ('https:' == document.location.protocol ? 
@@ -190,9 +214,11 @@
     var s = document.getElementsByTagName('script')[0]; 
 s.parentNode.insertBefore(ga, s);
   })();</script>";
-         $googleAnalyticsImageUrl = googleAnalyticsGetImageUrl();
-  echo '<noscript><img src="' . $googleAnalyticsImageUrl . '" /></noscript>';
-    }
+		$googleAnalyticsImageUrl = googleAnalyticsGetImageUrl();
+		echo '<noscript><img src="' . $googleAnalyticsImageUrl . '" /></noscript>';
+
+	}
+			echo "\n</div></div></body></html>";
 }
 function timePlaceSettings($geolocate = false)
 {
@@ -208,7 +234,7 @@
 	}
 	echo '<div data-role="collapsible" data-collapsed="' . !$geoerror . '">
         <h3>Change Time/Place (' . (isset($_SESSION['time']) ? $_SESSION['time'] : "Current Time,") . ' ' . ucwords(service_period()) . ')...</h3>
-        <form action="'.basename($_SERVER['PHP_SELF'])."?".$_SERVER['QUERY_STRING'].'" method="post">
+        <form action="' . basename($_SERVER['PHP_SELF']) . "?" . $_SERVER['QUERY_STRING'] . '" method="post">
         <div class="ui-body"> 
 		<div data-role="fieldcontain">
 	            <label for="geolocate"> Current Location: </label>
@@ -217,7 +243,7 @@
     		<div data-role="fieldcontain">
 		        <label for="time"> Time: </label>
 		    	<input type="time" name="time" id="time" value="' . (isset($_SESSION['time']) ? $_SESSION['time'] : date("H:i")) . '"/>
-			<a href="#" name="currentTime" id="currentTime" onClick="var d = new Date();'. "$('#time').val(d.getHours() +':'+ (d.getMinutes().toString().length = 1 ? '0'+ d.getMinutes():  d.getMinutes()));".'">Current Time?</a>
+			<a href="#" name="currentTime" id="currentTime" onClick="var d = new Date();' . "$('#time').val(d.getHours() +':'+ (d.getMinutes().toString().length == 1 ? '0'+ d.getMinutes():  d.getMinutes()));" . '">Current Time?</a>
 	        </div>
 		<div data-role="fieldcontain">
 		    <label for="service_period"> Service Period:  </label>
@@ -226,17 +252,18 @@
 		echo "<option value=\"$service_period\"" . (service_period() === $service_period ? " SELECTED" : "") . '>' . ucwords($service_period) . '</option>';
 	}
 	echo '</select>
-			<a href="#" style="display:none" name="currentPeriod" id="currentPeriod"/>Current Period?</a>
+			<a href="#" style="display:none" name="currentPeriod" id="currentPeriod">Current Period?</a>
 		</div>
 		
 		<input type="submit" value="Update"/>
-                </form>
-            </div></div>';
-}
-function trackEvent($category, $action, $label = "", $value = -1) {
-  if (isAnalyticsOn()) {
-    echo "<script> _gaq.push(['_trackEvent', $category, $action".($label != "" ? ", $label" : "").($value != -1 ? ", $value" : "")."]);</script>";
-  }
+                </div></form>
+            </div>';
+}
+function trackEvent($category, $action, $label = "", $value = - 1)
+{
+	if (isAnalyticsOn()) {
+		echo "\n<script> _gaq.push(['_trackEvent', '$category', '$action'" . ($label != "" ? ", '$label'" : "") . ($value != - 1 ? ", $value" : "") . "]);</script>";
+	}
 }
 ?>
 

--- a/include/common-transit.inc.php
+++ b/include/common-transit.inc.php
@@ -4,9 +4,26 @@
 	'saturday',
 	'weekday'
 );
+function getServiceOverride() {
+	global $conn;
+	$query = "Select * from calendar_dates where date = '".date("Ymd")."' and exception_type = '1'";
+	 debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_assoc($result);
+}
 function service_period()
 {
+	
 	if (isset($_SESSION['service_period'])) return $_SESSION['service_period'];
+	$override = getServiceOverride();
+	if ($override['service_id']){
+		return $override['service_id'];
+	}
+
 	switch (date('w')) {
 	case 0:
 		return 'sunday';
@@ -35,39 +52,5 @@
 		return "";
 	}
 }
-function viaPoints($tripid, $stopid, $timingPointsOnly = false)
-{
-	global $APIurl;
-	$url = $APIurl . "/json/tripstoptimes?trip=" . $tripid;
-	$json = json_decode(getPage($url));
-	debug(print_r($json, true));
-	$stops = $json[0];
-	$times = $json[1];
-	$foundStop = false;
-	$viaPoints = Array();
-	foreach ($stops as $key => $row) {
-		if ($foundStop) {
-			if (!$timingPointsOnly || !startsWith($row[5], "Wj")) {
-				$viaPoints[] = Array(
-					"id" => $row[0],
-					"name" => $row[1],
-					"time" => $times[$key]
-				);
-			}
-		}
-		else {
-			if ($row[0] == $stopid) $foundStop = true;
-		}
-	}
-	return $viaPoints;
-}
-function viaPointNames($tripid, $stopid)
-{
-	$points = viaPoints($tripid, $stopid, true);
-	$pointNames = Array();
-	foreach ($points as $point) {
-		$pointNames[] = $point['name'];
-	}
-	return implode(", ", $pointNames);
-}
 ?>
+

--- a/include/common.inc.php
+++ b/include/common.inc.php
@@ -1,24 +1,23 @@
 <?php
 date_default_timezone_set('Australia/ACT');
-$APIurl = "http://localhost:8765";
 $debugOkay = Array(
 	"session",
 	"json",
 	"phperror",
-	//"awsgtfs",
 	"awsotp",
 	//"squallotp",
-	//"vanilleotp",
+	"vanilleotp",
+	"database",
 	"other"
 );
-if (isDebug("awsgtfs")) {
-	$APIurl = "http://bus-main.lambdacomplex.org:8765";
-}
 $cloudmadeAPIkey = "daa03470bb8740298d4b10e3f03d63e6";
 $googleMapsAPIkey = "ABQIAAAA95XYXN0cki3Yj_Sb71CFvBTPaLd08ONybQDjcH_VdYtHHLgZvRTw2INzI_m17_IoOUqH3RNNmlTk1Q";
 $otpAPIurl = 'http://localhost:8080/opentripplanner-api-webapp/';
 if (isDebug("awsotp") || php_uname('n') == "maxious.xen.prgmr.com") {
 	$otpAPIurl = 'http://bus-main.lambdacomplex.org:8080/opentripplanner-api-webapp/';
+}
+if (isDebug("dotcloudotp") || php_uname('n') == "actbus-www") {
+	$otpAPIurl = 'http://otp.actbus.dotcloud.com/opentripplanner-api-webapp/';
 }
 if (isDebug("squallotp")) {
 		$otpAPIurl = 'http://10.0.1.108:5080/opentripplanner-api-webapp/';
@@ -31,9 +30,10 @@
 include_once ("common-geo.inc.php");
 include_once ("common-net.inc.php");
 include_once ("common-transit.inc.php");
-
 include_once ("common-session.inc.php");
+include_once ("common-db.inc.php");
 include_once ("common-template.inc.php");
+include_once ("common-request.inc.php");
 
 function isDebugServer()
 {
@@ -143,5 +143,42 @@
 	if ($sort_ascending) $array = array_reverse($temp_array);
 	else $array = $temp_array;
 }
+function sktimesort(&$array, $subkey = "id", $sort_ascending = false)
+{
+	if (count($array)) $temp_array[key($array) ] = array_shift($array);
+	foreach ($array as $key => $val) {
+		$offset = 0;
+		$found = false;
+		foreach ($temp_array as $tmp_key => $tmp_val) {
+			if (!$found and strtotime($val[$subkey]) > strtotime($tmp_val[$subkey])) {
+				$temp_array = array_merge((array)array_slice($temp_array, 0, $offset) , array(
+					$key => $val
+				) , array_slice($temp_array, $offset));
+				$found = true;
+			}
+			$offset++;
+		}
+		if (!$found) $temp_array = array_merge($temp_array, array(
+			$key => $val
+		));
+	}
+	if ($sort_ascending) $array = array_reverse($temp_array);
+	else $array = $temp_array;
+}
+function r_implode( $glue, $pieces ) 
+{ 
+  foreach( $pieces as $r_pieces ) 
+  { 
+    if( is_array( $r_pieces ) ) 
+    { 
+      $retVal[] = r_implode( $glue, $r_pieces ); 
+    } 
+    else 
+    { 
+      $retVal[] = $r_pieces; 
+    } 
+  } 
+  return implode( $glue, $retVal ); 
+} 
 ?>
 

--- /dev/null
+++ b/include/db/route-dao.inc.php
@@ -1,1 +1,160 @@
+<?php
 
+function getRoute($routeID) {
+		global $conn;
+        $query = "Select * from routes where route_id = '$routeID' LIMIT 1";
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_assoc($result);   
+}
+function getRoutes() {
+    	global $conn;
+	$query = "Select * from routes order by route_short_name;";
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);    
+}
+
+function getRoutesByNumber($routeNumber = "") {
+  	global $conn;
+        if ($routeNumber != "") {
+       	$query = "Select distinct routes.route_id,routes.route_short_name,routes.route_long_name,service_id from routes  join trips on trips.route_id =
+routes.route_id join stop_times on stop_times.trip_id = trips.trip_id where route_short_name = '$routeNumber' order by route_short_name;";
+        } else {
+            $query = "SELECT DISTINCT route_short_name from routes order by route_short_name";
+        }
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);    
+}
+
+function getRouteNextTrip($routeID) {
+     global $conn;
+    $query = "select * from routes join trips on trips.route_id = routes.route_id
+join stop_times on stop_times.trip_id = trips.trip_id where
+arrival_time > '".current_time()."' and routes.route_id = '$routeID' order by
+arrival_time limit 1";
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {   
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+        $r = pg_fetch_assoc($result);   
+        // past last trip of the day special case
+       if (sizeof($r) == 0) {
+            $query = "select * from routes join trips on trips.route_id = routes.route_id
+join stop_times on stop_times.trip_id = trips.trip_id where routes.route_id = '$routeID' order by
+arrival_time DESC limit 1";
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {   
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+        $r = pg_fetch_assoc($result); 
+       }
+	return $r;       
+  }
+  
+  function getTimeInterpolatedRouteAtStop($routeID, $stop_id)
+{
+    $nextTrip = getRouteNextTrip($routeID);
+    if ($nextTrip['trip_id']){
+    	foreach (getTimeInterpolatedTrip($nextTrip['trip_id']) as $tripStop) {
+		if ($tripStop['stop_id'] == $stop_id) return $tripStop;
+	}
+    }
+	return Array();
+}
+  
+function getRouteTrips($routeID) {
+        global $conn;
+    $query = "select routes.route_id,trips.trip_id,service_id,arrival_time, stop_id, stop_sequence from routes join trips on trips.route_id = routes.route_id
+join stop_times on stop_times.trip_id = trips.trip_id where routes.route_id = '$routeID' and stop_sequence = '1' order by
+arrival_time ";
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);       
+  }
+function getRoutesByDestination($destination = "", $service_period = "") {
+    global $conn;
+         if ($service_period == "") $service_period = service_period();
+         if ($destination != "")  {
+             $query = "SELECT DISTINCT trips.route_id,route_short_name,route_long_name, service_id
+FROM stop_times join trips on trips.trip_id =
+stop_times.trip_id join routes on trips.route_id = routes.route_id
+WHERE route_long_name = '$destination' AND  service_id='$service_period' order by route_short_name";
+         } else {
+        $query = "SELECT DISTINCT route_long_name
+FROM stop_times join trips on trips.trip_id =
+stop_times.trip_id join routes on trips.route_id = routes.route_id
+WHERE service_id='$service_period' order by route_long_name";
+    }
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+
+function getRoutesBySuburb($suburb, $service_period = "") {
+         if ($service_period == "") $service_period = service_period();
+    global $conn;
+        $query = "SELECT DISTINCT service_id,trips.route_id,route_short_name,route_long_name
+FROM stop_times join trips on trips.trip_id = stop_times.trip_id
+join routes on trips.route_id = routes.route_id
+join stops on stops.stop_id = stop_times.stop_id
+WHERE zone_id LIKE '%$suburb;%' AND service_id='$service_period' ORDER BY route_short_name";
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+
+function getRoutesNearby($lat, $lng, $limit = "", $distance = 500) {
+
+        
+                 if ($service_period == "") $service_period = service_period();
+                  if ($limit != "") $limit = " LIMIT $limit "; 
+    global $conn;
+        $query = "SELECT service_id,trips.route_id,route_short_name,route_long_name,min(stops.stop_id) as stop_id,
+        min(ST_Distance(position, ST_GeographyFromText('SRID=4326;POINT($lng $lat)'), FALSE)) as distance
+FROM stop_times
+join trips on trips.trip_id = stop_times.trip_id
+join routes on trips.route_id = routes.route_id
+join stops on stops.stop_id = stop_times.stop_id
+WHERE service_id='$service_period'
+AND ST_DWithin(position, ST_GeographyFromText('SRID=4326;POINT($lng $lat)'), $distance, FALSE)
+        group by service_id,trips.route_id,route_short_name,route_long_name
+        order by distance $limit";
+        debug($query,"database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+?>

--- /dev/null
+++ b/include/db/stop-dao.inc.php
@@ -1,1 +1,144 @@
-
+<?php
+function getStop($stopID)
+{
+	global $conn;
+	$query = "Select * from stops where stop_id = '$stopID' LIMIT 1";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_assoc($result);
+}
+function getStops($timingPointsOnly = false, $firstLetter = "")
+{
+	global $conn;
+	$conditions = Array();
+	if ($timingPointsOnly) $conditions[] = "substr(stop_code,1,2) != 'Wj'";
+	if ($firstLetter != "") $conditions[] = "substr(stop_name,1,1) = '$firstLetter'";
+	$query = "Select * from stops";
+	if (sizeof($conditions) > 0) {
+		if (sizeof($conditions) > 1) {
+			$query.= " Where " . implode(" AND ", $conditions) . " ";
+		}
+		else {
+			$query.= " Where " . $conditions[0] . " ";
+		}
+	}
+	$query.= " order by stop_name;";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+function getNearbyStops($lat, $lng, $limit = "", $distance = 1000)
+{
+	if ($lat == null || $lng == null) return Array();
+	if ($limit != "") $limit = " LIMIT $limit ";
+	global $conn;
+	$query = "Select *, ST_Distance(position, ST_GeographyFromText('SRID=4326;POINT($lng $lat)'), FALSE) as distance
+        from stops WHERE ST_DWithin(position, ST_GeographyFromText('SRID=4326;POINT($lng $lat)'), $distance, FALSE)
+        order by distance $limit;";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+function getStopsBySuburb($suburb)
+{
+	global $conn;
+	$query = "Select * from stops where zone_id LIKE '%$suburb;%' order by stop_name;";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+function getStopRoutes($stopID, $service_period)
+{
+	if ($service_period == "") $service_period = service_period();
+	global $conn;
+	$query = "SELECT service_id,trips.route_id,route_short_name,route_long_name
+FROM stop_times join trips on trips.trip_id =
+stop_times.trip_id join routes on trips.route_id = routes.route_id WHERE stop_id = '$stopID' AND service_id='$service_period'";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+function getStopTrips($stopID, $service_period = "", $afterTime = "")
+{
+	if ($service_period == "") $service_period = service_period();
+	$afterCondition = "AND arrival_time > '$afterTime'";
+	global $conn;
+	if ($afterTime != "") {
+		$query = " SELECT stop_times.trip_id,stop_times.arrival_time,stop_times.stop_id,stop_sequence,service_id,trips.route_id,route_short_name,route_long_name, end_times.arrival_time as end_time
+FROM stop_times
+join trips on trips.trip_id =
+stop_times.trip_id
+join routes on trips.route_id = routes.route_id , (SELECT trip_id,max(arrival_time) as arrival_time from stop_times
+	WHERE stop_times.arrival_time IS NOT NULL group by trip_id) as end_times 
+WHERE stop_times.stop_id = '$stopID'
+AND stop_times.trip_id = end_times.trip_id
+AND service_id='$service_period'
+AND end_times.arrival_time > '$afterTime'
+ORDER BY end_time";
+	}
+	else {
+		$query = "SELECT stop_times.trip_id,arrival_time,stop_times.stop_id,stop_sequence,service_id,trips.route_id,route_short_name,route_long_name
+FROM stop_times
+join trips on trips.trip_id =
+stop_times.trip_id
+join routes on trips.route_id = routes.route_id
+WHERE stop_times.stop_id = '$stopID'
+AND service_id='$service_period'
+ORDER BY arrival_time";
+	}
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+function getStopTripsWithTimes($stopID, $time = "", $service_period = "", $time_range = "", $limit = "")
+{
+	if ($service_period == "") $service_period = service_period();
+	if ($time_range == "") $time_range = (24 * 60 * 60);
+	if ($time == "") $time = current_time();
+	if ($limit == "") $limit = 10;
+	$trips = getStopTrips($stopID, $service_period, $time);
+	$timedTrips = Array();
+	if ($trips && sizeof($trips) > 0) {
+            foreach ($trips as $trip) {
+		if ($trip['arrival_time'] != "") {
+			if (strtotime($trip['arrival_time']) > strtotime($time) and strtotime($trip['arrival_time']) < (strtotime($time) + $time_range)) {
+				$timedTrips[] = $trip;
+			}
+		}
+		else {
+			$timedTrip = getTimeInterpolatedTripAtStop($trip['trip_id'], $trip['stop_sequence']);
+			if ($timedTrip['arrival_time'] > $time and strtotime($timedTrip['arrival_time']) < (strtotime($time) + $time_range)) {
+				$timedTrips[] = $timedTrip;
+			}
+		}
+		if (sizeof($timedTrips) > $limit) break;
+	}
+	sktimesort($timedTrips, "arrival_time", true);
+        }
+	return $timedTrips;
+}
+?>

--- /dev/null
+++ b/include/db/trip-dao.inc.php
@@ -1,1 +1,186 @@
+<?php
+function getTrip($tripID)
+{
+	global $conn;
+	$query = "Select * from trips
+	join routes on trips.route_id = routes.route_id
+	where trip_id = '$tripID'
+	LIMIT 1";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_assoc($result);
+}
+function getTripShape()
+{
+	/* def handle_json_GET_tripstopTimes(self, params):
+	   schedule = self.server.schedule
+	   try:
+	     trip = schedule.GetTrip(params.get('trip'))
+	   except KeyError:
+	      # if a non-existent trip is searched for, the return nothing
+	     return
+	   time_stops = trip.GetTimeInterpolatedStops()
+	   stops = []
+	   times = []
+	   for arr,ts,is_timingpoint in time_stops:
+	     stops.append(StopToTuple(ts.stop))
+	     times.append(arr)
+	   return [stops, times]
+	
+	 def handle_json_GET_tripshape(self, params):
+	   schedule = self.server.schedule
+	   try:
+	     trip = schedule.GetTrip(params.get('trip'))
+	   except KeyError:
+	      # if a non-existent trip is searched for, the return nothing
+	     return
+	   points = []
+	   if trip.shape_id:
+	     shape = schedule.GetShape(trip.shape_id)
+	     for (lat, lon, dist) in shape.points:
+	       points.append((lat, lon))
+	   else:
+	     time_stops = trip.GetTimeStops()
+	     for arr,dep,stop in time_stops:
+	       points.append((stop.stop_lat, stop.stop_lon))
+	   return points*/
+}
+function getTimeInterpolatedTrip($tripID, $range = "")
+{
+	global $conn;
+	$query = "SELECT stop_times.trip_id,arrival_time,stop_times.stop_id,stop_lat,stop_lon,stop_name,stop_code,
+	stop_sequence,service_id,trips.route_id,route_short_name,route_long_name
+FROM stop_times
+join trips on trips.trip_id = stop_times.trip_id
+join routes on trips.route_id = routes.route_id
+join stops on stops.stop_id = stop_times.stop_id
+WHERE trips.trip_id = '$tripID' $range ORDER BY stop_sequence";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	$stopTimes = pg_fetch_all($result);
+	$cur_timepoint = Array();
+	$next_timepoint = Array();
+	$distance_between_timepoints = 0.0;
+	$distance_traveled_between_timepoints = 0.0;
+	$rv = Array();
+	foreach ($stopTimes as $i => $stopTime) {
+		if ($stopTime['arrival_time'] != "") {
+		    // is timepoint
+			$cur_timepoint = $stopTime;
+			$distance_between_timepoints = 0.0;
+			$distance_traveled_between_timepoints = 0.0;
+			if ($i + 1 < sizeof($stopTimes)) {
+				$k = $i + 1;
+				$distance_between_timepoints += distance($stopTimes[$k - 1]["stop_lat"], $stopTimes[$k - 1]["stop_lon"], $stopTimes[$k]["stop_lat"], $stopTimes[$k]["stop_lon"]);
+				while ($stopTimes[$k]["arrival_time"] == "" && $k + 1 < sizeof($stopTimes)) {
+					$k += 1;
+					//echo "k".$k;
+					$distance_between_timepoints += distance($stopTimes[$k - 1]["stop_lat"], $stopTimes[$k - 1]["stop_lon"], $stopTimes[$k]["stop_lat"], $stopTimes[$k]["stop_lon"]);
+				}
+				$next_timepoint = $stopTimes[$k];
+				$rv[] = $stopTime;
+			}
+		}
+		else {
+		    // is untimed point
+		    //echo "i".$i;
+			$distance_traveled_between_timepoints += distance($stopTimes[$i - 1]["stop_lat"], $stopTimes[$i - 1]["stop_lon"], $stopTimes[$i]["stop_lat"], $stopTimes[$i]["stop_lon"]);
+			//echo "$distance_traveled_between_timepoints / $distance_between_timepoints<br>";
+			$distance_percent = $distance_traveled_between_timepoints / $distance_between_timepoints;
+			if ($next_timepoint["arrival_time"] != "") {
+			$total_time = strtotime($next_timepoint["arrival_time"]) - strtotime($cur_timepoint["arrival_time"]);
+			//echo strtotime($next_timepoint["arrival_time"])." - ".strtotime($cur_timepoint["arrival_time"])."<br>";
+			$time_estimate = ($distance_percent * $total_time) + strtotime($cur_timepoint["arrival_time"]);
+			$stopTime["arrival_time"] = date("H:i:s", $time_estimate);
+			} else {
+			    $stopTime["arrival_time"] = $cur_timepoint["arrival_time"];
+			}
+			$rv[] = $stopTime;
+			//var_dump($rv);
+		}
+	}
+	return $rv;
+}
+function getTimeInterpolatedTripAtStop($tripID, $stop_sequence)
+{
+    global $conn;
+    // limit interpolation to between nearest actual points.
+    $prevTimePoint = pg_fetch_assoc(pg_query($conn," SELECT trip_id,stop_id,
+	stop_sequence
+FROM stop_times
+WHERE trip_id = '$tripID' and stop_sequence < $stop_sequence and stop_times.arrival_time IS NOT NULL ORDER BY stop_sequence DESC LIMIT 1"));
+    $nextTimePoint = pg_fetch_assoc(pg_query($conn," SELECT trip_id,stop_id,
+	stop_sequence
+FROM stop_times
+WHERE trip_id = '$tripID' and stop_sequence > $stop_sequence and stop_times.arrival_time IS NOT NULL ORDER BY stop_sequence LIMIT 1"));
+    $range = "AND stop_sequence >= '{$prevTimePoint['stop_sequence']}' AND stop_sequence <= '{$nextTimePoint['stop_sequence']}'";
+    	foreach (getTimeInterpolatedTrip($tripID,$range) as $tripStop) {
+		if ($tripStop['stop_sequence'] == $stop_sequence) return $tripStop;
+	}
+	return Array();
+}
+function getTripStartTime($tripID)
+{
+    	global $conn;
+	$query = "Select * from stop_times
+	where trip_id = '$tripID'
+	AND arrival_time IS NOT NULL
+	AND stop_sequence = '1'";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	$r = pg_fetch_assoc($result);
+	return $r['arrival_time'];
+}
+function getActiveTrips($time)
+{
+    	global $conn;
+	if ($time == "") $time = current_time();
+	$query = "Select distinct stop_times.trip_id, start_times.arrival_time as start_time, end_times.arrival_time as end_time from stop_times, (SELECT trip_id,arrival_time from stop_times WHERE stop_times.arrival_time IS NOT NULL
+AND stop_sequence = '1') as start_times, (SELECT trip_id,max(arrival_time) as arrival_time from stop_times WHERE stop_times.arrival_time IS NOT NULL group by trip_id) as end_times
+WHERE start_times.trip_id = end_times.trip_id AND stop_times.trip_id = end_times.trip_id AND $time > start_times.arrival_time  AND $time < end_times.arrival_time";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
 
+function viaPoints($tripid, $stop_sequence = "")
+{
+	global $conn;
+	$query = "SELECT stops.stop_id, stop_name, arrival_time
+FROM stop_times join stops on stops.stop_id = stop_times.stop_id
+WHERE stop_times.trip_id = '$tripid'
+".($stop_sequence != "" ? "AND stop_sequence > '$stop_sequence'" : "").
+"AND substr(stop_code,1,2) != 'Wj' ORDER BY stop_sequence";
+	debug($query, "database");
+	$result = pg_query($conn, $query);
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	return pg_fetch_all($result);
+}
+function viaPointNames($tripid, $stop_sequence = "")
+{
+	$viaPointNames = Array();
+	foreach(viaPoints($tripid, $stop_sequence) as $point) {
+		$viaPointNames[] = $point['stop_name'];
+	}
+	return r_implode(", ", $viaPointNames);
+}
+?>

file:a/index.php -> file:b/index.php
--- a/index.php
+++ b/index.php
@@ -13,18 +13,19 @@
                 <li data-role="list-divider">Timetables - Stops</li>
                 <li><a href="stopList.php">Major (Timing Point) Stops</a></li>
 		<li><a href="stopList.php?allstops=yes">All Stops</a></li>
-		<li><a href="stopList.php?suburbs=yes">Stops By Suburb</a></li>
+		<li><a href="stopList.php?bysuburbs=yes">Stops By Suburb</a></li>
 		<li><a class="nearby" href="stopList.php?nearby=yes">Nearby Stops</a></li>
             </ul>
 	    <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
                 <li data-role="list-divider">Timetables - Routes</li>
                 <li><a href="routeList.php">Routes By Final Destination</a></li>
 		<li><a href="routeList.php?bynumber=yes">Routes By Number</a></li>
-		<li><a href="routeList.php?bysuburb=yes">Routes By Suburb</a></li>
+		<li><a href="routeList.php?bysuburbs=yes">Routes By Suburb</a></li>
 		<li><a class="nearby" href="routeList.php?nearby=yes">Nearby Routes</a></li>
             </ul>
 <?php
 echo timePlaceSettings();
+echo ' <a href="labs/index.php" data-role="button" data-icon="beaker">Busness R&amp;D</a>';
 include_footer(true)
 ?>
         

--- /dev/null
+++ b/js/flotr/flotr-0.2.0-alpha.js
@@ -1,1 +1,2 @@
-
+//Flotr 0.2.0-alpha Copyright (c) 2009 Bas Wenneker, <http://solutoire.com>, MIT License.
+var Flotr={version:"0.2.0-alpha",author:"Bas Wenneker",website:"http://www.solutoire.com",_registeredTypes:{lines:"drawSeriesLines",points:"drawSeriesPoints",bars:"drawSeriesBars",candles:"drawSeriesCandles",pie:"drawSeriesPie"},register:function(A,B){Flotr._registeredTypes[A]=B+""},draw:function(B,D,A,C){C=C||Flotr.Graph;return new C(B,D,A)},getSeries:function(A){return A.collect(function(C){var B,C=(C.data)?Object.clone(C):{data:C};for(B=C.data.length-1;B>-1;--B){C.data[B][1]=(C.data[B][1]===null?null:parseFloat(C.data[B][1]))}return C})},merge:function(D,B){var A=B||{};for(var C in D){A[C]=(D[C]!=null&&typeof (D[C])=="object"&&!(D[C].constructor==Array||D[C].constructor==RegExp)&&!Object.isElement(D[C]))?Flotr.merge(D[C],B[C]):A[C]=D[C]}return A},getTickSize:function(E,D,A,B){var H=(A-D)/E;var G=Flotr.getMagnitude(H);var C=H/G;var F=10;if(C<1.5){F=1}else{if(C<2.25){F=2}else{if(C<3){F=((B==0)?2:2.5)}else{if(C<7.5){F=5}}}}return F*G},defaultTickFormatter:function(A){return A+""},defaultTrackFormatter:function(A){return"("+A.x+", "+A.y+")"},defaultPieLabelFormatter:function(A){return(A.fraction*100).toFixed(2)+"%"},getMagnitude:function(A){return Math.pow(10,Math.floor(Math.log(A)/Math.LN10))},toPixel:function(A){return Math.floor(A)+0.5},toRad:function(A){return -A*(Math.PI/180)},parseColor:function(D){if(D instanceof Flotr.Color){return D}var A,C=Flotr.Color;if((A=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(D))){return new C(parseInt(A[1]),parseInt(A[2]),parseInt(A[3]))}if((A=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(D))){return new C(parseInt(A[1]),parseInt(A[2]),parseInt(A[3]),parseFloat(A[4]))}if((A=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(D))){return new C(parseFloat(A[1])*2.55,parseFloat(A[2])*2.55,parseFloat(A[3])*2.55)}if((A=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(D))){return new C(parseFloat(A[1])*2.55,parseFloat(A[2])*2.55,parseFloat(A[3])*2.55,parseFloat(A[4]))}if((A=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(D))){return new C(parseInt(A[1],16),parseInt(A[2],16),parseInt(A[3],16))}if((A=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(D))){return new C(parseInt(A[1]+A[1],16),parseInt(A[2]+A[2],16),parseInt(A[3]+A[3],16))}var B=D.strip().toLowerCase();if(B=="transparent"){return new C(255,255,255,0)}return((A=C.lookupColors[B]))?new C(A[0],A[1],A[2]):false},extractColor:function(B){var A;do{A=B.getStyle("background-color").toLowerCase();if(!(A==""||A=="transparent")){break}B=B.up(0)}while(!B.nodeName.match(/^body$/i));return(A=="rgba(0, 0, 0, 0)")?"transparent":A}};Flotr.Graph=Class.create({initialize:function(B,C,A){this.el=$(B);if(!this.el){throw"The target container doesn't exist"}this.data=C;this.series=Flotr.getSeries(C);this.setOptions(A);this.lastMousePos={pageX:null,pageY:null};this.selection={first:{x:-1,y:-1},second:{x:-1,y:-1}};this.prevSelection=null;this.selectionInterval=null;this.ignoreClick=false;this.prevHit=null;this.constructCanvas();this.initEvents();this.findDataRanges();this.calculateTicks(this.axes.x);this.calculateTicks(this.axes.x2);this.calculateTicks(this.axes.y);this.calculateTicks(this.axes.y2);this.calculateSpacing();this.draw();this.insertLegend();if(this.options.spreadsheet.show){this.constructTabs()}},setOptions:function(B){var P={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],title:null,subtitle:null,legend:{show:true,noColumns:1,labelFormatter:Prototype.K,labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,container:null,position:"nw",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{ticks:null,showLabels:true,labelsAngle:0,title:null,titleAngle:0,noTicks:5,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscaleMargin:0,color:null},x2axis:{},yaxis:{ticks:null,showLabels:true,labelsAngle:0,title:null,titleAngle:90,noTicks:5,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscaleMargin:0,color:null},y2axis:{titleAngle:270},points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#FFFFFF",fillOpacity:0.4},lines:{show:false,lineWidth:2,fill:false,fillColor:null,fillOpacity:0.4},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,fillOpacity:0.4,horizontal:false,stacked:false},candles:{show:false,lineWidth:1,wickLineWidth:1,candleWidth:0.6,fill:true,upFillColor:"#00A8F0",downFillColor:"#CB4B4B",fillOpacity:0.5,barcharts:false},pie:{show:false,lineWidth:1,fill:true,fillColor:null,fillOpacity:0.6,explode:6,sizeRatio:0.6,startAngle:Math.PI/4,labelFormatter:Flotr.defaultPieLabelFormatter,pie3D:false,pie3DviewAngle:(Math.PI/2*0.8),pie3DspliceThickness:20},grid:{color:"#545454",backgroundColor:null,tickColor:"#DDDDDD",labelMargin:3,verticalLines:true,horizontalLines:true,outlineWidth:2},selection:{mode:null,color:"#B6D9FF",fps:20},mouse:{track:false,position:"se",relative:false,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,radius:3},shadowSize:4,defaultType:"lines",HtmlText:true,fontSize:7.5,spreadsheet:{show:false,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all"}};P.x2axis=Object.extend(Object.clone(P.xaxis),P.x2axis);P.y2axis=Object.extend(Object.clone(P.yaxis),P.y2axis);this.options=Flotr.merge((B||{}),P);this.axes={x:{options:this.options.xaxis,n:1},x2:{options:this.options.x2axis,n:2},y:{options:this.options.yaxis,n:1},y2:{options:this.options.y2axis,n:2}};var H=[],C=[],K=this.series.length,N=this.series.length,D=this.options.colors,A=[],G=0,M,J,I,O,E;for(J=N-1;J>-1;--J){M=this.series[J].color;if(M!=null){--N;if(Object.isNumber(M)){H.push(M)}else{A.push(Flotr.parseColor(M))}}}for(J=H.length-1;J>-1;--J){N=Math.max(N,H[J]+1)}for(J=0;C.length<N;){M=(D.length==J)?new Flotr.Color(100,100,100):Flotr.parseColor(D[J]);var F=G%2==1?-1:1;var L=1+F*Math.ceil(G/2)*0.2;M.scale(L,L,L);C.push(M);if(++J>=D.length){J=0;++G}}for(J=0,I=0;J<K;++J){O=this.series[J];if(O.color==null){O.color=C[I++].toString()}else{if(Object.isNumber(O.color)){O.color=C[O.color].toString()}}if(!O.xaxis){O.xaxis=this.axes.x}if(O.xaxis==1){O.xaxis=this.axes.x}else{if(O.xaxis==2){O.xaxis=this.axes.x2}}if(!O.yaxis){O.yaxis=this.axes.y}if(O.yaxis==1){O.yaxis=this.axes.y}else{if(O.yaxis==2){O.yaxis=this.axes.y2}}O.lines=Object.extend(Object.clone(this.options.lines),O.lines);O.points=Object.extend(Object.clone(this.options.points),O.points);O.bars=Object.extend(Object.clone(this.options.bars),O.bars);O.candles=Object.extend(Object.clone(this.options.candles),O.candles);O.pie=Object.extend(Object.clone(this.options.pie),O.pie);O.mouse=Object.extend(Object.clone(this.options.mouse),O.mouse);if(O.shadowSize==null){O.shadowSize=this.options.shadowSize}}},constructCanvas:function(){var C=this.el,B,D,A;this.canvas=C.select(".flotr-canvas")[0];this.overlay=C.select(".flotr-overlay")[0];C.childElements().invoke("remove");C.setStyle({position:"relative",cursor:"default"});this.canvasWidth=C.getWidth();this.canvasHeight=C.getHeight();B={width:this.canvasWidth,height:this.canvasHeight};if(this.canvasWidth<=0||this.canvasHeight<=0){throw"Invalid dimensions for plot, width = "+this.canvasWidth+", height = "+this.canvasHeight}if(!this.canvas){D=this.canvas=new Element("canvas",B);D.className="flotr-canvas";D=D.writeAttribute("style","position:absolute;left:0px;top:0px;")}else{D=this.canvas.writeAttribute(B)}C.insert(D);if(Prototype.Browser.IE){D=window.G_vmlCanvasManager.initElement(D)}this.ctx=D.getContext("2d");if(!this.overlay){A=this.overlay=new Element("canvas",B);A.className="flotr-overlay";A=A.writeAttribute("style","position:absolute;left:0px;top:0px;")}else{A=this.overlay.writeAttribute(B)}C.insert(A);if(Prototype.Browser.IE){A=window.G_vmlCanvasManager.initElement(A)}this.octx=A.getContext("2d");if(window.CanvasText){CanvasText.enable(this.ctx);CanvasText.enable(this.octx);this.textEnabled=true}},getTextDimensions:function(F,C,B,D){if(!F){return{width:0,height:0}}if(!this.options.HtmlText&&this.textEnabled){var E=this.ctx.getTextBounds(F,C);return{width:E.width+2,height:E.height+6}}else{var A=this.el.insert('<div style="position:absolute;top:-10000px;'+B+'" class="'+D+' flotr-dummy-div">'+F+"</div>").select(".flotr-dummy-div")[0];dim=A.getDimensions();A.remove();return dim}},loadDataGrid:function(){if(this.seriesData){return this.seriesData}var A=this.series;var B=[];for(i=0;i<A.length;++i){A[i].data.each(function(D){var C=D[0],F=D[1];if(r=B.find(function(G){return G[0]==C})){r[i+1]=F}else{var E=[];E[0]=C;E[i+1]=F;B.push(E)}})}B=B.sortBy(function(C){return C[0]});return this.seriesData=B},showTab:function(B,C){var A="canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle";switch(B){case"graph":this.datagrid.up().hide();this.el.select(A).invoke("show");this.tabs.data.removeClassName("selected");this.tabs.graph.addClassName("selected");break;case"data":this.constructDataGrid();this.datagrid.up().show();this.el.select(A).invoke("hide");this.tabs.data.addClassName("selected");this.tabs.graph.removeClassName("selected");break}},constructTabs:function(){var A=new Element("div",{className:"flotr-tabs-group",style:"position:absolute;left:0px;top:"+this.canvasHeight+"px;width:"+this.canvasWidth+"px;"});this.el.insert({bottom:A});this.tabs={graph:new Element("div",{className:"flotr-tab selected",style:"float:left;"}).update(this.options.spreadsheet.tabGraphLabel),data:new Element("div",{className:"flotr-tab",style:"float:left;"}).update(this.options.spreadsheet.tabDataLabel)};A.insert(this.tabs.graph).insert(this.tabs.data);this.el.setStyle({height:this.canvasHeight+this.tabs.data.getHeight()+2+"px"});this.tabs.graph.observe("click",(function(){this.showTab("graph")}).bind(this));this.tabs.data.observe("click",(function(){this.showTab("data")}).bind(this))},constructDataGrid:function(){if(this.datagrid){return this.datagrid}var D,B,L=this.series,J=this.loadDataGrid();var K=this.datagrid=new Element("table",{className:"flotr-datagrid",style:"height:100px;"});var C=["<colgroup><col />"];var F=['<tr class="first-row">'];F.push("<th>&nbsp;</th>");for(D=0;D<L.length;++D){F.push('<th scope="col">'+(L[D].label||String.fromCharCode(65+D))+"</th>");C.push("<col />")}F.push("</tr>");for(B=0;B<J.length;++B){F.push("<tr>");for(D=0;D<L.length+1;++D){var M="td";var G=(J[B][D]!=null?Math.round(J[B][D]*100000)/100000:"");if(D==0){M="th";var I;if(this.options.xaxis.ticks){var E=this.options.xaxis.ticks.find(function(N){return N[0]==J[B][D]});if(E){I=E[1]}}else{I=this.options.xaxis.tickFormatter(G)}if(I){G=I}}F.push("<"+M+(M=="th"?' scope="row"':"")+">"+G+"</"+M+">")}F.push("</tr>")}C.push("</colgroup>");K.update(C.join("")+F.join(""));if(!Prototype.Browser.IE){K.select("td").each(function(N){N.observe("mouseover",function(O){N=O.element();var P=N.previousSiblings();K.select("th[scope=col]")[P.length-1].addClassName("hover");K.select("colgroup col")[P.length].addClassName("hover")});N.observe("mouseout",function(){K.select("colgroup col.hover, th.hover").each(function(O){O.removeClassName("hover")})})})}var H=new Element("div",{className:"flotr-datagrid-toolbar"}).insert(new Element("button",{type:"button",className:"flotr-datagrid-toolbar-button"}).update(this.options.spreadsheet.toolbarDownload).observe("click",this.downloadCSV.bind(this))).insert(new Element("button",{type:"button",className:"flotr-datagrid-toolbar-button"}).update(this.options.spreadsheet.toolbarSelectAll).observe("click",this.selectAllData.bind(this)));var A=new Element("div",{className:"flotr-datagrid-container",style:"left:0px;top:0px;width:"+this.canvasWidth+"px;height:"+this.canvasHeight+"px;overflow:auto;"});A.insert(H);K.wrap(A.hide());this.el.insert(A);return K},selectAllData:function(){if(this.tabs){var B,A,E,D,C=this.constructDataGrid();this.showTab("data");(function(){if((E=C.ownerDocument)&&(D=E.defaultView)&&D.getSelection&&E.createRange&&(B=window.getSelection())&&B.removeAllRanges){A=E.createRange();A.selectNode(C);B.removeAllRanges();B.addRange(A)}else{if(document.body&&document.body.createTextRange&&(A=document.body.createTextRange())){A.moveToElementText(C);A.select()}}}).defer();return true}else{return false}},downloadCSV:function(){var D,A='"x"',C=this.series,E=this.loadDataGrid();for(D=0;D<C.length;++D){A+='%09"'+(C[D].label||String.fromCharCode(65+D))+'"'}A+="%0D%0A";for(D=0;D<E.length;++D){if(this.options.xaxis.ticks){var B=this.options.xaxis.ticks.find(function(F){return F[0]==E[D][0]});if(B){E[D][0]=B[1]}}else{E[D][0]=this.options.xaxis.tickFormatter(E[D][0])}A+=E[D].join("%09")+"%0D%0A"}if(Prototype.Browser.IE){A=A.gsub("%09","\t").gsub("%0A","\n").gsub("%0D","\r");window.open().document.write(A)}else{window.open("data:text/csv,"+A)}},initEvents:function(){this.overlay.stopObserving();this.overlay.observe("mousedown",this.mouseDownHandler.bind(this));this.overlay.observe("mousemove",this.mouseMoveHandler.bind(this));this.overlay.observe("click",this.clickHandler.bind(this))},findDataRanges:function(){var J=this.series,G=this.axes;G.x.datamin=0;G.x.datamax=0;G.x2.datamin=0;G.x2.datamax=0;G.y.datamin=0;G.y.datamax=0;G.y2.datamin=0;G.y2.datamax=0;if(J.length>0){var C,A,D,H,F,B,I,E;for(C=0;C<J.length;++C){B=J[C].data,I=J[C].xaxis,E=J[C].yaxis;if(B.length>0&&!J[C].hide){if(!I.used){I.datamin=I.datamax=B[0][0]}if(!E.used){E.datamin=E.datamax=B[0][1]}I.used=true;E.used=true;for(D=B.length-1;D>-1;--D){H=B[D][0];if(H<I.datamin){I.datamin=H}else{if(H>I.datamax){I.datamax=H}}for(A=1;A<B[D].length;A++){F=B[D][A];if(F<E.datamin){E.datamin=F}else{if(F>E.datamax){E.datamax=F}}}}}}}this.findXAxesValues();this.calculateRange(G.x);this.extendXRangeIfNeededByBar(G.x);if(G.x2.used){this.calculateRange(G.x2);this.extendXRangeIfNeededByBar(G.x2)}this.calculateRange(G.y);this.extendYRangeIfNeededByBar(G.y);if(G.y2.used){this.calculateRange(G.y2);this.extendYRangeIfNeededByBar(G.y2)}},calculateRange:function(D){var F=D.options,C=F.min!=null?F.min:D.datamin,A=F.max!=null?F.max:D.datamax,E;if(A-C==0){var B=(A==0)?1:0.01;C-=B;A+=B}D.tickSize=Flotr.getTickSize(F.noTicks,C,A,F.tickDecimals);if(F.min==null){E=F.autoscaleMargin;if(E!=0){C-=D.tickSize*E;if(C<0&&D.datamin>=0){C=0}C=D.tickSize*Math.floor(C/D.tickSize)}}if(F.max==null){E=F.autoscaleMargin;if(E!=0){A+=D.tickSize*E;if(A>0&&D.datamax<=0){A=0}A=D.tickSize*Math.ceil(A/D.tickSize)}}D.min=C;D.max=A},extendXRangeIfNeededByBar:function(A){if(A.options.max==null){var D=A.max,B,I,F,E,H=[],C=null;for(B=0;B<this.series.length;++B){I=this.series[B];F=I.bars;E=I.candles;if(I.axis==A&&(F.show||E.show)){if(!F.horizontal&&(F.barWidth+A.datamax>D)||(E.candleWidth+A.datamax>D)){D=A.max+I.bars.barWidth}if(F.stacked&&F.horizontal){for(j=0;j<I.data.length;j++){if(I.bars.show&&I.bars.stacked){var G=I.data[j][0];H[G]=(H[G]||0)+I.data[j][1];C=I}}for(j=0;j<H.length;j++){D=Math.max(H[j],D)}}}}A.lastSerie=C;A.max=D}},extendYRangeIfNeededByBar:function(A){if(A.options.max==null){var D=A.max,B,I,F,E,H=[],C=null;for(B=0;B<this.series.length;++B){I=this.series[B];F=I.bars;E=I.candles;if(I.yaxis==A&&F.show&&!I.hide){if(F.horizontal&&(F.barWidth+A.datamax>D)||(E.candleWidth+A.datamax>D)){D=A.max+F.barWidth}if(F.stacked&&!F.horizontal){for(j=0;j<I.data.length;j++){if(I.bars.show&&I.bars.stacked){var G=I.data[j][0];H[G]=(H[G]||0)+I.data[j][1];C=I}}for(j=0;j<H.length;j++){D=Math.max(H[j],D)}}}}A.lastSerie=C;A.max=D}},findXAxesValues:function(){for(i=this.series.length-1;i>-1;--i){s=this.series[i];s.xaxis.values=s.xaxis.values||[];for(j=s.data.length-1;j>-1;--j){s.xaxis.values[s.data[j][0]]={}}}},calculateTicks:function(D){var B=D.options,E,H;D.ticks=[];if(B.ticks){var G=B.ticks,I,F;if(Object.isFunction(G)){G=G({min:D.min,max:D.max})}for(E=0;E<G.length;++E){I=G[E];if(typeof (I)=="object"){H=I[0];F=(I.length>1)?I[1]:B.tickFormatter(H)}else{H=I;F=B.tickFormatter(H)}D.ticks[E]={v:H,label:F}}}else{var A=D.tickSize*Math.ceil(D.min/D.tickSize),C;for(E=0;A+E*D.tickSize<=D.max;++E){H=A+E*D.tickSize;C=B.tickDecimals;if(C==null){C=1-Math.floor(Math.log(D.tickSize)/Math.LN10)}if(C<0){C=0}H=H.toFixed(C);D.ticks.push({v:H,label:B.tickFormatter(H)})}}},calculateSpacing:function(){var L=this.axes,N=this.options,H=this.series,D=N.grid.labelMargin,M=L.x,A=L.x2,J=L.y,K=L.y2,F=2,G,E,C,I;[M,A,J,K].each(function(P){var O="";if(P.options.showLabels){for(G=0;G<P.ticks.length;++G){C=P.ticks[G].label.length;if(C>O.length){O=P.ticks[G].label}}}P.maxLabel=this.getTextDimensions(O,{size:N.fontSize,angle:Flotr.toRad(P.options.labelsAngle)},"font-size:smaller;","flotr-grid-label");P.titleSize=this.getTextDimensions(P.options.title,{size:N.fontSize*1.2,angle:Flotr.toRad(P.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},this);I=this.getTextDimensions(N.title,{size:N.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title");this.titleHeight=I.height;I=this.getTextDimensions(N.subtitle,{size:N.fontSize},"font-size:smaller;","flotr-subtitle");this.subtitleHeight=I.height;if(N.show){F=Math.max(F,N.points.radius+N.points.lineWidth/2)}for(E=0;E<N.length;++E){if(H[E].points.show){F=Math.max(F,H[E].points.radius+H[E].points.lineWidth/2)}}var B=this.plotOffset={left:0,right:0,top:0,bottom:0};B.left=B.right=B.top=B.bottom=F;B.bottom+=(M.options.showLabels?(M.maxLabel.height+D):0)+(M.options.title?(M.titleSize.height+D):0);B.top+=(A.options.showLabels?(A.maxLabel.height+D):0)+(A.options.title?(A.titleSize.height+D):0)+this.subtitleHeight+this.titleHeight;B.left+=(J.options.showLabels?(J.maxLabel.width+D):0)+(J.options.title?(J.titleSize.width+D):0);B.right+=(K.options.showLabels?(K.maxLabel.width+D):0)+(K.options.title?(K.titleSize.width+D):0);B.top=Math.floor(B.top);this.plotWidth=this.canvasWidth-B.left-B.right;this.plotHeight=this.canvasHeight-B.bottom-B.top;M.scale=this.plotWidth/(M.max-M.min);A.scale=this.plotWidth/(A.max-A.min);J.scale=this.plotHeight/(J.max-J.min);K.scale=this.plotHeight/(K.max-K.min)},draw:function(){this.drawGrid();this.drawLabels();this.drawTitles();if(this.series.length){this.el.fire("flotr:beforedraw",[this.series,this]);for(var A=0;A<this.series.length;A++){if(!this.series[A].hide){this.drawSeries(this.series[A])}}}this.el.fire("flotr:afterdraw",[this.series,this])},tHoz:function(A,B){B=B||this.axes.x;return(A-B.min)*B.scale},tVert:function(B,A){A=A||this.axes.y;return this.plotHeight-(B-A.min)*A.scale},drawGrid:function(){var B,E=this.options,A=this.ctx;if(E.grid.verticalLines||E.grid.horizontalLines){this.el.fire("flotr:beforegrid",[this.axes.x,this.axes.y,E,this])}A.save();A.translate(this.plotOffset.left,this.plotOffset.top);if(E.grid.backgroundColor!=null){A.fillStyle=E.grid.backgroundColor;A.fillRect(0,0,this.plotWidth,this.plotHeight)}A.lineWidth=1;A.strokeStyle=E.grid.tickColor;A.beginPath();if(E.grid.verticalLines){for(var D=0;D<this.axes.x.ticks.length;++D){B=this.axes.x.ticks[D].v;if((B==this.axes.x.min||B==this.axes.x.max)&&E.grid.outlineWidth!=0){continue}A.moveTo(Math.floor(this.tHoz(B))+A.lineWidth/2,0);A.lineTo(Math.floor(this.tHoz(B))+A.lineWidth/2,this.plotHeight)}}if(E.grid.horizontalLines){for(var C=0;C<this.axes.y.ticks.length;++C){B=this.axes.y.ticks[C].v;if((B==this.axes.y.min||B==this.axes.y.max)&&E.grid.outlineWidth!=0){continue}A.moveTo(0,Math.floor(this.tVert(B))+A.lineWidth/2);A.lineTo(this.plotWidth,Math.floor(this.tVert(B))+A.lineWidth/2)}}A.stroke();if(E.grid.outlineWidth!=0){A.lineWidth=E.grid.outlineWidth;A.strokeStyle=E.grid.color;A.lineJoin="round";A.strokeRect(0,0,this.plotWidth,this.plotHeight)}A.restore();if(E.grid.verticalLines||E.grid.horizontalLines){this.el.fire("flotr:aftergrid",[this.axes.x,this.axes.y,E,this])}},drawLabels:function(){var C=0,D,B,E,F,G,J=this.options,I=this.ctx,H=this.axes;for(E=0;E<H.x.ticks.length;++E){if(H.x.ticks[E].label){++C}}B=this.plotWidth/C;if(!J.HtmlText&&this.textEnabled){var A={size:J.fontSize,adjustAlign:true};D=H.x;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="c";A.valign="t";I.drawText(G.label,this.plotOffset.left+this.tHoz(G.v,D),this.plotOffset.top+this.plotHeight+J.grid.labelMargin,A)}D=H.x2;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="c";A.valign="b";I.drawText(G.label,this.plotOffset.left+this.tHoz(G.v,D),this.plotOffset.top+J.grid.labelMargin,A)}D=H.y;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="r";A.valign="m";I.drawText(G.label,this.plotOffset.left-J.grid.labelMargin,this.plotOffset.top+this.tVert(G.v,D),A)}D=H.y2;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="l";A.valign="m";I.drawText(G.label,this.plotOffset.left+this.plotWidth+J.grid.labelMargin,this.plotOffset.top+this.tVert(G.v,D),A);I.save();I.strokeStyle=A.color;I.beginPath();I.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+this.tVert(G.v,D));I.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+this.tVert(G.v,D));I.stroke();I.restore()}}else{if(H.x.options.showLabels||H.x2.options.showLabels||H.y.options.showLabels||H.y2.options.showLabels){F=['<div style="font-size:smaller;color:'+J.grid.color+';" class="flotr-labels">'];D=H.x;if(D.options.showLabels){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight+J.grid.labelMargin)+"px;left:"+(this.plotOffset.left+this.tHoz(G.v,D)-B/2)+"px;width:"+B+"px;text-align:center;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.x2;if(D.options.showLabels&&D.used){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top-J.grid.labelMargin-D.maxLabel.height)+"px;left:"+(this.plotOffset.left+this.tHoz(G.v,D)-B/2)+"px;width:"+B+"px;text-align:center;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.y;if(D.options.showLabels){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.tVert(G.v,D)-D.maxLabel.height/2)+"px;left:0;width:"+(this.plotOffset.left-J.grid.labelMargin)+"px;text-align:right;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.y2;if(D.options.showLabels&&D.used){I.save();I.strokeStyle=D.options.color||J.grid.color;I.beginPath();for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.tVert(G.v,D)-D.maxLabel.height/2)+"px;right:0;width:"+(this.plotOffset.right-J.grid.labelMargin)+"px;text-align:left;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>");I.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+this.tVert(G.v,D));I.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+this.tVert(G.v,D))}I.stroke();I.restore()}F.push("</div>");this.el.insert(F.join(""))}}},drawTitles:function(){var D,C=this.options,F=C.grid.labelMargin,B=this.ctx,A=this.axes;if(!C.HtmlText&&this.textEnabled){var E={size:C.fontSize,color:C.grid.color,halign:"c"};if(C.subtitle){B.drawText(C.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,E)}E.weight=1.5;E.size*=1.5;if(C.title){B.drawText(C.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,E)}E.weight=1.8;E.size*=0.8;E.adjustAlign=true;if(A.x.options.title&&A.x.used){E.halign="c";E.valign="t";E.angle=Flotr.toRad(A.x.options.titleAngle);B.drawText(A.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+A.x.maxLabel.height+this.plotHeight+2*F,E)}if(A.x2.options.title&&A.x2.used){E.halign="c";E.valign="b";E.angle=Flotr.toRad(A.x2.options.titleAngle);B.drawText(A.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-A.x2.maxLabel.height-2*F,E)}if(A.y.options.title&&A.y.used){E.halign="r";E.valign="m";E.angle=Flotr.toRad(A.y.options.titleAngle);B.drawText(A.y.options.title,this.plotOffset.left-A.y.maxLabel.width-2*F,this.plotOffset.top+this.plotHeight/2,E)}if(A.y2.options.title&&A.y2.used){E.halign="l";E.valign="m";E.angle=Flotr.toRad(A.y2.options.titleAngle);B.drawText(A.y2.options.title,this.plotOffset.left+this.plotWidth+A.y2.maxLabel.width+2*F,this.plotOffset.top+this.plotHeight/2,E)}}else{D=['<div style="color:'+C.grid.color+';" class="flotr-titles">'];if(C.title){D.push('<div style="position:absolute;top:0;left:'+this.plotOffset.left+"px;font-size:1em;font-weight:bold;text-align:center;width:"+this.plotWidth+'px;" class="flotr-title">'+C.title+"</div>")}if(C.subtitle){D.push('<div style="position:absolute;top:'+this.titleHeight+"px;left:"+this.plotOffset.left+"px;font-size:smaller;text-align:center;width:"+this.plotWidth+'px;" class="flotr-subtitle">'+C.subtitle+"</div>")}D.push("</div>");D.push('<div class="flotr-axis-title" style="font-weight:bold;">');if(A.x.options.title&&A.x.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight+C.grid.labelMargin+A.x.titleSize.height)+"px;left:"+this.plotOffset.left+"px;width:"+this.plotWidth+'px;text-align:center;" class="flotr-axis-title">'+A.x.options.title+"</div>")}if(A.x2.options.title&&A.x2.used){D.push('<div style="position:absolute;top:0;left:'+this.plotOffset.left+"px;width:"+this.plotWidth+'px;text-align:center;" class="flotr-axis-title">'+A.x2.options.title+"</div>")}if(A.y.options.title&&A.y.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight/2-A.y.titleSize.height/2)+'px;left:0;text-align:right;" class="flotr-axis-title">'+A.y.options.title+"</div>")}if(A.y2.options.title&&A.y2.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight/2-A.y.titleSize.height/2)+'px;right:0;text-align:right;" class="flotr-axis-title">'+A.y2.options.title+"</div>")}D.push("</div>");this.el.insert(D.join(""))}},drawSeries:function(A){A=A||this.series;var C=false;for(var B in Flotr._registeredTypes){if(A[B]&&A[B].show){this[Flotr._registeredTypes[B]](A);C=true}}if(!C){this[Flotr._registeredTypes[this.options.defaultType]](A)}},plotLine:function(I,F){var O=this.ctx,A=I.xaxis,K=I.yaxis,J=this.tHoz.bind(this),M=this.tVert.bind(this),H=I.data;if(H.length<2){return }var E=J(H[0][0],A),D=M(H[0][1],K)+F;O.beginPath();O.moveTo(E,D);for(var G=0;G<H.length-1;++G){var C=H[G][0],N=H[G][1],B=H[G+1][0],L=H[G+1][1];if(N===null||L===null){continue}if(N<=L&&N<K.min){if(L<K.min){continue}C=(K.min-N)/(L-N)*(B-C)+C;N=K.min}else{if(L<=N&&L<K.min){if(N<K.min){continue}B=(K.min-N)/(L-N)*(B-C)+C;L=K.min}}if(N>=L&&N>K.max){if(L>K.max){continue}C=(K.max-N)/(L-N)*(B-C)+C;N=K.max}else{if(L>=N&&L>K.max){if(N>K.max){continue}B=(K.max-N)/(L-N)*(B-C)+C;L=K.max}}if(C<=B&&C<A.min){if(B<A.min){continue}N=(A.min-C)/(B-C)*(L-N)+N;C=A.min}else{if(B<=C&&B<A.min){if(C<A.min){continue}L=(A.min-C)/(B-C)*(L-N)+N;B=A.min}}if(C>=B&&C>A.max){if(B>A.max){continue}N=(A.max-C)/(B-C)*(L-N)+N;C=A.max}else{if(B>=C&&B>A.max){if(C>A.max){continue}L=(A.max-C)/(B-C)*(L-N)+N;B=A.max}}if(E!=J(C,A)||D!=M(N,K)+F){O.moveTo(J(C,A),M(N,K)+F)}E=J(B,A);D=M(L,K)+F;O.lineTo(E,D)}O.stroke()},plotLineArea:function(J,D){var S=J.data;if(S.length<2){return }var L,G=0,N=this.ctx,Q=J.xaxis,B=J.yaxis,E=this.tHoz.bind(this),M=this.tVert.bind(this),H=Math.min(Math.max(0,B.min),B.max),F=true;N.beginPath();for(var O=0;O<S.length-1;++O){var R=S[O][0],C=S[O][1],P=S[O+1][0],A=S[O+1][1];if(R<=P&&R<Q.min){if(P<Q.min){continue}C=(Q.min-R)/(P-R)*(A-C)+C;R=Q.min}else{if(P<=R&&P<Q.min){if(R<Q.min){continue}A=(Q.min-R)/(P-R)*(A-C)+C;P=Q.min}}if(R>=P&&R>Q.max){if(P>Q.max){continue}C=(Q.max-R)/(P-R)*(A-C)+C;R=Q.max}else{if(P>=R&&P>Q.max){if(R>Q.max){continue}A=(Q.max-R)/(P-R)*(A-C)+C;P=Q.max}}if(F){N.moveTo(E(R,Q),M(H,B)+D);F=false}if(C>=B.max&&A>=B.max){N.lineTo(E(R,Q),M(B.max,B)+D);N.lineTo(E(P,Q),M(B.max,B)+D);continue}else{if(C<=B.min&&A<=B.min){N.lineTo(E(R,Q),M(B.min,B)+D);N.lineTo(E(P,Q),M(B.min,B)+D);continue}}var I=R,K=P;if(C<=A&&C<B.min&&A>=B.min){R=(B.min-C)/(A-C)*(P-R)+R;C=B.min}else{if(A<=C&&A<B.min&&C>=B.min){P=(B.min-C)/(A-C)*(P-R)+R;A=B.min}}if(C>=A&&C>B.max&&A<=B.max){R=(B.max-C)/(A-C)*(P-R)+R;C=B.max}else{if(A>=C&&A>B.max&&C<=B.max){P=(B.max-C)/(A-C)*(P-R)+R;A=B.max}}if(R!=I){L=(C<=B.min)?L=B.min:B.max;N.lineTo(E(I,Q),M(L,B)+D);N.lineTo(E(R,Q),M(L,B)+D)}N.lineTo(E(R,Q),M(C,B)+D);N.lineTo(E(P,Q),M(A,B)+D);if(P!=K){L=(A<=B.min)?B.min:B.max;N.lineTo(E(K,Q),M(L,B)+D);N.lineTo(E(P,Q),M(L,B)+D)}G=Math.max(P,K)}N.lineTo(E(G,Q),M(H,B)+D);N.closePath();N.fill()},drawSeriesLines:function(C){C=C||this.series;var B=this.ctx;B.save();B.translate(this.plotOffset.left,this.plotOffset.top);B.lineJoin="round";var D=C.lines.lineWidth;var A=C.shadowSize;if(A>0){B.lineWidth=A/2;var E=D/2+B.lineWidth/2;B.strokeStyle="rgba(0,0,0,0.1)";this.plotLine(C,E+A/2);B.strokeStyle="rgba(0,0,0,0.2)";this.plotLine(C,E);if(C.lines.fill){B.fillStyle="rgba(0,0,0,0.05)";this.plotLineArea(C,E+A/2)}}B.lineWidth=D;B.strokeStyle=C.color;if(C.lines.fill){B.fillStyle=C.lines.fillColor!=null?C.lines.fillColor:Flotr.parseColor(C.color).scale(null,null,null,C.lines.fillOpacity).toString();this.plotLineArea(C,0)}this.plotLine(C,0);B.restore()},drawSeriesPoints:function(C){var B=this.ctx;B.save();B.translate(this.plotOffset.left,this.plotOffset.top);var D=C.lines.lineWidth;var A=C.shadowSize;if(A>0){B.lineWidth=A/2;B.strokeStyle="rgba(0,0,0,0.1)";this.plotPointShadows(C,A/2+B.lineWidth/2,C.points.radius);B.strokeStyle="rgba(0,0,0,0.2)";this.plotPointShadows(C,B.lineWidth/2,C.points.radius)}B.lineWidth=C.points.lineWidth;B.strokeStyle=C.color;B.fillStyle=C.points.fillColor!=null?C.points.fillColor:C.color;this.plotPoints(C,C.points.radius,C.points.fill);B.restore()},plotPoints:function(C,E,I){var A=C.xaxis,F=C.yaxis,J=this.ctx,D,B=C.data;for(D=B.length-1;D>-1;--D){var H=B[D][0],G=B[D][1];if(H<A.min||H>A.max||G<F.min||G>F.max){continue}J.beginPath();J.arc(this.tHoz(H,A),this.tVert(G,F),E,0,2*Math.PI,true);if(I){J.fill()}J.stroke()}},plotPointShadows:function(D,B,F){var A=D.xaxis,G=D.yaxis,J=this.ctx,E,C=D.data;for(E=C.length-1;E>-1;--E){var I=C[E][0],H=C[E][1];if(I<A.min||I>A.max||H<G.min||H>G.max){continue}J.beginPath();J.arc(this.tHoz(I,A),this.tVert(H,G)+B,F,0,Math.PI,false);J.stroke()}},drawSeriesBars:function(B){var A=this.ctx,D=B.bars.barWidth,C=Math.min(B.bars.lineWidth,D);A.save();A.translate(this.plotOffset.left,this.plotOffset.top);A.lineJoin="miter";A.lineWidth=C;A.strokeStyle=B.color;this.plotBarsShadows(B,D,0,B.bars.fill);if(B.bars.fill){A.fillStyle=B.bars.fillColor!=null?B.bars.fillColor:Flotr.parseColor(B.color).scale(null,null,null,B.bars.fillOpacity).toString()}this.plotBars(B,D,0,B.bars.fill);A.restore()},plotBars:function(K,N,D,Q){var U=K.data;if(U.length<1){return }var S=K.xaxis,B=K.yaxis,P=this.ctx,F=this.tHoz.bind(this),O=this.tVert.bind(this);for(var R=0;R<U.length;R++){var J=U[R][0],I=U[R][1];var E=true,L=true,A=true;var H=0;if(K.bars.stacked){S.values.each(function(W,V){if(V==J){H=W.stack||0;W.stack=H+I}})}if(K.bars.horizontal){var C=H,T=J+H,G=I,M=I+N}else{var C=J,T=J+N,G=H,M=I+H}if(T<S.min||C>S.max||M<B.min||G>B.max){continue}if(C<S.min){C=S.min;E=false}if(T>S.max){T=S.max;if(S.lastSerie!=K&&K.bars.horizontal){L=false}}if(G<B.min){G=B.min}if(M>B.max){M=B.max;if(B.lastSerie!=K&&!K.bars.horizontal){L=false}}if(Q){P.beginPath();P.moveTo(F(C,S),O(G,B)+D);P.lineTo(F(C,S),O(M,B)+D);P.lineTo(F(T,S),O(M,B)+D);P.lineTo(F(T,S),O(G,B)+D);P.fill()}if(K.bars.lineWidth!=0&&(E||A||L)){P.beginPath();P.moveTo(F(C,S),O(G,B)+D);P[E?"lineTo":"moveTo"](F(C,S),O(M,B)+D);P[L?"lineTo":"moveTo"](F(T,S),O(M,B)+D);P[A?"lineTo":"moveTo"](F(T,S),O(G,B)+D);P.stroke()}}},plotBarsShadows:function(I,K,C){var T=I.data;if(T.length<1){return }var R=I.xaxis,A=I.yaxis,P=this.ctx,D=this.tHoz.bind(this),M=this.tVert.bind(this),N=this.options.shadowSize;for(var Q=0;Q<T.length;Q++){var H=T[Q][0],G=T[Q][1];var E=0;if(I.bars.stacked){R.values.each(function(V,U){if(U==H){E=V.stackShadow||0;V.stackShadow=E+G}})}if(I.bars.horizontal){var B=E,S=H+E,F=G,J=G+K}else{var B=H,S=H+K,F=E,J=G+E}if(S<R.min||B>R.max||J<A.min||F>A.max){continue}if(B<R.min){B=R.min}if(S>R.max){S=R.max}if(F<A.min){F=A.min}if(J>A.max){J=A.max}var O=D(S,R)-D(B,R)-((D(S,R)+N<=this.plotWidth)?0:N);var L=Math.max(0,M(F,A)-M(J,A)-((M(F,A)+N<=this.plotHeight)?0:N));P.fillStyle="rgba(0,0,0,0.05)";P.fillRect(Math.min(D(B,R)+N,this.plotWidth),Math.min(M(J,A)+N,this.plotWidth),O,L)}},drawSeriesCandles:function(B){var A=this.ctx,C=B.candles.candleWidth;A.save();A.translate(this.plotOffset.left,this.plotOffset.top);A.lineJoin="miter";A.lineWidth=B.candles.lineWidth;this.plotCandlesShadows(B,C/2);this.plotCandles(B,C/2);A.restore()},plotCandles:function(K,D){var W=K.data;if(W.length<1){return }var T=K.xaxis,B=K.yaxis,P=this.ctx,E=this.tHoz.bind(this),O=this.tVert.bind(this);for(var S=0;S<W.length;S++){var U=W[S],J=U[0],L=U[1],I=U[2],X=U[3],N=U[4];var C=J,V=J+K.candles.candleWidth,G=Math.max(B.min,X),M=Math.min(B.max,I),A=Math.max(B.min,Math.min(L,N)),R=Math.min(B.max,Math.max(L,N));if(V<T.min||C>T.max||M<B.min||G>B.max){continue}var Q=K.candles[L>N?"downFillColor":"upFillColor"];if(K.candles.fill&&!K.candles.barcharts){P.fillStyle=Flotr.parseColor(Q).scale(null,null,null,K.candles.fillOpacity).toString();P.fillRect(E(C,T),O(R,B)+D,E(V,T)-E(C,T),O(A,B)-O(R,B))}if(K.candles.lineWidth||K.candles.wickLineWidth){var J,H,F=(K.candles.wickLineWidth%2)/2;J=Math.floor(E((C+V)/2),T)+F;P.save();P.strokeStyle=Q;P.lineWidth=K.candles.wickLineWidth;P.lineCap="butt";if(K.candles.barcharts){P.beginPath();P.moveTo(J,Math.floor(O(M,B)+D));P.lineTo(J,Math.floor(O(G,B)+D));H=Math.floor(O(L,B)+D)+0.5;P.moveTo(Math.floor(E(C,T))+F,H);P.lineTo(J,H);H=Math.floor(O(N,B)+D)+0.5;P.moveTo(Math.floor(E(V,T))+F,H);P.lineTo(J,H)}else{P.strokeRect(E(C,T),O(R,B)+D,E(V,T)-E(C,T),O(A,B)-O(R,B));P.beginPath();P.moveTo(J,Math.floor(O(R,B)+D));P.lineTo(J,Math.floor(O(M,B)+D));P.moveTo(J,Math.floor(O(A,B)+D));P.lineTo(J,Math.floor(O(G,B)+D))}P.stroke();P.restore()}}},plotCandlesShadows:function(H,C){var T=H.data;if(T.length<1||H.candles.barcharts){return }var Q=H.xaxis,A=H.yaxis,D=this.tHoz.bind(this),M=this.tVert.bind(this),N=this.options.shadowSize;for(var P=0;P<T.length;P++){var R=T[P],G=R[0],I=R[1],F=R[2],U=R[3],K=R[4];var B=G,S=G+H.candles.candleWidth,E=Math.max(A.min,Math.min(I,K)),J=Math.min(A.max,Math.max(I,K));if(S<Q.min||B>Q.max||J<A.min||E>A.max){continue}var O=D(S,Q)-D(B,Q)-((D(S,Q)+N<=this.plotWidth)?0:N);var L=Math.max(0,M(E,A)-M(J,A)-((M(E,A)+N<=this.plotHeight)?0:N));this.ctx.fillStyle="rgba(0,0,0,0.05)";this.ctx.fillRect(Math.min(D(B,Q)+N,this.plotWidth),Math.min(M(J,A)+N,this.plotWidth),O,L)}},drawSeriesPie:function(G){if(!this.options.pie.drawn){var K=this.ctx,C=this.options,E=G.pie.lineWidth,I=G.shadowSize,R=G.data,D=(Math.min(this.canvasWidth,this.canvasHeight)*G.pie.sizeRatio)/2,H=[];var L=1;var P=Math.sin(G.pie.viewAngle)*G.pie.spliceThickness/L;var M={size:C.fontSize*1.2,color:C.grid.color,weight:1.5};var Q={x:(this.canvasWidth+this.plotOffset.left)/2,y:(this.canvasHeight-this.plotOffset.bottom)/2};var O=this.series.collect(function(T,S){if(T.pie.show){return{name:(T.label||T.data[0][1]),value:[S,T.data[0][1]],explode:T.pie.explode}}});var B=O.pluck("value").pluck(1).inject(0,function(S,T){return S+T});var F=0,N=G.pie.startAngle,J=0;var A=O.collect(function(S){N+=F;J=parseFloat(S.value[1]);F=J/B;return{name:S.name,fraction:F,x:S.value[0],y:J,explode:S.explode,startAngle:2*N*Math.PI,endAngle:2*(N+F)*Math.PI}});K.save();if(I>0){A.each(function(V){var S=(V.startAngle+V.endAngle)/2;var T=Q.x+Math.cos(S)*V.explode+I;var U=Q.y+Math.sin(S)*V.explode+I;this.plotSlice(T,U,D,V.startAngle,V.endAngle,false,L);K.fillStyle="rgba(0,0,0,0.1)";K.fill()},this)}if(C.HtmlText){H=['<div style="color:'+this.options.grid.color+'" class="flotr-labels">']}A.each(function(c,X){var W=(c.startAngle+c.endAngle)/2;var V=C.colors[X];var Y=Q.x+Math.cos(W)*c.explode;var U=Q.y+Math.sin(W)*c.explode;this.plotSlice(Y,U,D,c.startAngle,c.endAngle,false,L);if(G.pie.fill){K.fillStyle=Flotr.parseColor(V).scale(null,null,null,G.pie.fillOpacity).toString();K.fill()}K.lineWidth=E;K.strokeStyle=V;K.stroke();var b=C.pie.labelFormatter(c);var S=(Math.cos(W)<0);var a=Y+Math.cos(W)*(G.pie.explode+D);var Z=U+Math.sin(W)*(G.pie.explode+D);if(c.fraction&&b){if(C.HtmlText){var T="position:absolute;top:"+(Z-5)+"px;";if(S){T+="right:"+(this.canvasWidth-a)+"px;text-align:right;"}else{T+="left:"+a+"px;text-align:left;"}H.push('<div style="'+T+'" class="flotr-grid-label">'+b+"</div>")}else{M.halign=S?"r":"l";K.drawText(b,a,Z+M.size/2,M)}}},this);if(C.HtmlText){H.push("</div>");this.el.insert(H.join(""))}K.restore();C.pie.drawn=true}},plotSlice:function(B,H,A,E,D,F,G){var C=this.ctx;G=G||1;C.save();C.scale(1,G);C.beginPath();C.moveTo(B,H);C.arc(B,H,A,E,D,F);C.lineTo(B,H);C.closePath();C.restore()},plotPie:function(){},insertLegend:function(){if(!this.options.legend.show){return }var H=this.series,I=this.plotOffset,B=this.options,b=[],A=false,O=this.ctx,R;var Q=H.findAll(function(c){return(c.label&&!c.hide)}).size();if(Q){if(!B.HtmlText&&this.textEnabled){var T={size:B.fontSize*1.1,color:B.grid.color};var M=B.legend.position,N=B.legend.margin,L=B.legend.labelBoxWidth,Z=B.legend.labelBoxHeight,S=B.legend.labelBoxMargin,W=I.left+N,U=I.top+N;var a=0;for(R=H.length-1;R>-1;--R){if(!H[R].label||H[R].hide){continue}var E=B.legend.labelFormatter(H[R].label);a=Math.max(a,O.measureText(E,T))}var K=Math.round(L+S*3+a),C=Math.round(Q*(S+Z)+S);if(M.charAt(0)=="s"){U=I.top+this.plotHeight-(N+C)}if(M.charAt(1)=="e"){W=I.left+this.plotWidth-(N+K)}var P=Flotr.parseColor(B.legend.backgroundColor||"rgb(240,240,240)").scale(null,null,null,B.legend.backgroundOpacity||0.1).toString();O.fillStyle=P;O.fillRect(W,U,K,C);O.strokeStyle=B.legend.labelBoxBorderColor;O.strokeRect(Flotr.toPixel(W),Flotr.toPixel(U),K,C);var G=W+S;var F=U+S;for(R=0;R<H.length;R++){if(!H[R].label||H[R].hide){continue}var E=B.legend.labelFormatter(H[R].label);O.fillStyle=H[R].color;O.fillRect(G,F,L-1,Z-1);O.strokeStyle=B.legend.labelBoxBorderColor;O.lineWidth=1;O.strokeRect(Math.ceil(G)-1.5,Math.ceil(F)-1.5,L+2,Z+2);O.drawText(E,G+L+S,F+(Z+T.size-O.fontDescent(T))/2,T);F+=Z+S}}else{for(R=0;R<H.length;++R){if(!H[R].label||H[R].hide){continue}if(R%B.legend.noColumns==0){b.push(A?"</tr><tr>":"<tr>");A=true}var E=B.legend.labelFormatter(H[R].label);b.push('<td class="flotr-legend-color-box"><div style="border:1px solid '+B.legend.labelBoxBorderColor+';padding:1px"><div style="width:'+B.legend.labelBoxWidth+"px;height:"+B.legend.labelBoxHeight+"px;background-color:"+H[R].color+'"></div></div></td><td class="flotr-legend-label">'+E+"</td>")}if(A){b.push("</tr>")}if(b.length>0){var V='<table style="font-size:smaller;color:'+B.grid.color+'">'+b.join("")+"</table>";if(B.legend.container!=null){$(B.legend.container).update(V)}else{var D="";var M=B.legend.position,N=B.legend.margin;if(M.charAt(0)=="n"){D+="top:"+(N+I.top)+"px;"}else{if(M.charAt(0)=="s"){D+="bottom:"+(N+I.bottom)+"px;"}}if(M.charAt(1)=="e"){D+="right:"+(N+I.right)+"px;"}else{if(M.charAt(1)=="w"){D+="left:"+(N+I.left)+"px;"}}var J=this.el.insert('<div class="flotr-legend" style="position:absolute;z-index:2;'+D+'">'+V+"</div>").select("div.flotr-legend").first();if(B.legend.backgroundOpacity!=0){var Y=B.legend.backgroundColor;if(Y==null){var X=(B.grid.backgroundColor!=null)?B.grid.backgroundColor:Flotr.extractColor(J);Y=Flotr.parseColor(X).adjust(null,null,null,1).toString()}this.el.insert('<div class="flotr-legend-bg" style="position:absolute;width:'+J.getWidth()+"px;height:"+J.getHeight()+"px;"+D+"background-color:"+Y+';"> </div>').select("div.flotr-legend-bg").first().setStyle({opacity:B.legend.backgroundOpacity})}}}}}},getEventPosition:function(C){var G=this.overlay.cumulativeOffset(),F=(C.pageX-G.left-this.plotOffset.left),E=(C.pageY-G.top-this.plotOffset.top),D=0,B=0;if(C.pageX==null&&C.clientX!=null){var H=document.documentElement,A=document.body;D=C.clientX+(H&&H.scrollLeft||A.scrollLeft||0);B=C.clientY+(H&&H.scrollTop||A.scrollTop||0)}else{D=C.pageX;B=C.pageY}return{x:this.axes.x.min+F/this.axes.x.scale,x2:this.axes.x2.min+F/this.axes.x2.scale,y:this.axes.y.max-E/this.axes.y.scale,y2:this.axes.y2.max-E/this.axes.y2.scale,relX:F,relY:E,absX:D,absY:B}},clickHandler:function(A){if(this.ignoreClick){this.ignoreClick=false;return }this.el.fire("flotr:click",[this.getEventPosition(A),this])},mouseMoveHandler:function(A){var B=this.getEventPosition(A);this.lastMousePos.pageX=B.absX;this.lastMousePos.pageY=B.absY;if(this.selectionInterval==null&&(this.options.mouse.track||this.series.any(function(C){return C.mouse&&C.mouse.track}))){this.hit(B)}this.el.fire("flotr:mousemove",[A,B,this])},mouseDownHandler:function(C){if(C.isRightClick()){C.stop();var B=this.overlay;B.hide();function A(){B.show();$(document).stopObserving("mousemove",A)}$(document).observe("mousemove",A);return }if(!this.options.selection.mode||!C.isLeftClick()){return }this.setSelectionPos(this.selection.first,C);if(this.selectionInterval!=null){clearInterval(this.selectionInterval)}this.lastMousePos.pageX=null;this.selectionInterval=setInterval(this.updateSelection.bind(this),1000/this.options.selection.fps);this.mouseUpHandler=this.mouseUpHandler.bind(this);$(document).observe("mouseup",this.mouseUpHandler)},fireSelectEvent:function(){var A=this.axes,F=this.selection,C=(F.first.x<=F.second.x)?F.first.x:F.second.x,B=(F.first.x<=F.second.x)?F.second.x:F.first.x,E=(F.first.y>=F.second.y)?F.first.y:F.second.y,D=(F.first.y>=F.second.y)?F.second.y:F.first.y;C=A.x.min+C/A.x.scale;B=A.x.min+B/A.x.scale;E=A.y.max-E/A.y.scale;D=A.y.max-D/A.y.scale;this.el.fire("flotr:select",[{x1:C,y1:E,x2:B,y2:D},this])},mouseUpHandler:function(A){$(document).stopObserving("mouseup",this.mouseUpHandler);A.stop();if(this.selectionInterval!=null){clearInterval(this.selectionInterval);this.selectionInterval=null}this.setSelectionPos(this.selection.second,A);this.clearSelection();if(this.selectionIsSane()){this.drawSelection();this.fireSelectEvent();this.ignoreClick=true}},setSelectionPos:function(D,B){var A=this.options,C=$(this.overlay).cumulativeOffset();if(A.selection.mode.indexOf("x")==-1){D.x=(D==this.selection.first)?0:this.plotWidth}else{D.x=B.pageX-C.left-this.plotOffset.left;D.x=Math.min(Math.max(0,D.x),this.plotWidth)}if(A.selection.mode.indexOf("y")==-1){D.y=(D==this.selection.first)?0:this.plotHeight}else{D.y=B.pageY-C.top-this.plotOffset.top;D.y=Math.min(Math.max(0,D.y),this.plotHeight)}},updateSelection:function(){if(this.lastMousePos.pageX==null){return }this.setSelectionPos(this.selection.second,this.lastMousePos);this.clearSelection();if(this.selectionIsSane()){this.drawSelection()}},clearSelection:function(){if(this.prevSelection==null){return }var G=this.prevSelection,E=this.octx,C=this.plotOffset,A=Math.min(G.first.x,G.second.x),F=Math.min(G.first.y,G.second.y),B=Math.abs(G.second.x-G.first.x),D=Math.abs(G.second.y-G.first.y);E.clearRect(A+C.left-E.lineWidth,F+C.top-E.lineWidth,B+E.lineWidth*2,D+E.lineWidth*2);this.prevSelection=null},setSelection:function(G){var B=this.options,H=this.axes.x,A=this.axes.y,F=yaxis.scale,D=xaxis.scale,E=B.selection.mode.indexOf("x")!=-1,C=B.selection.mode.indexOf("y")!=-1;this.clearSelection();this.selection.first.y=E?0:(A.max-G.y1)*F;this.selection.second.y=E?this.plotHeight:(A.max-G.y2)*F;this.selection.first.x=C?0:(G.x1-H.min)*D;this.selection.second.x=C?this.plotWidth:(G.x2-H.min)*D;this.drawSelection();this.fireSelectEvent()},drawSelection:function(){var C=this.prevSelection,F=this.selection,H=this.octx,I=this.options,A=this.plotOffset;if(C!=null&&F.first.x==C.first.x&&F.first.y==C.first.y&&F.second.x==C.second.x&&F.second.y==C.second.y){return }H.strokeStyle=Flotr.parseColor(I.selection.color).scale(null,null,null,0.8).toString();H.lineWidth=1;H.lineJoin="round";H.fillStyle=Flotr.parseColor(I.selection.color).scale(null,null,null,0.4).toString();this.prevSelection={first:{x:F.first.x,y:F.first.y},second:{x:F.second.x,y:F.second.y}};var E=Math.min(F.first.x,F.second.x),D=Math.min(F.first.y,F.second.y),G=Math.abs(F.second.x-F.first.x),B=Math.abs(F.second.y-F.first.y);H.fillRect(E+A.left,D+A.top,G,B);H.strokeRect(E+A.left,D+A.top,G,B)},selectionIsSane:function(){var A=this.selection;return Math.abs(A.second.x-A.first.x)>=5&&Math.abs(A.second.y-A.first.y)>=5},clearHit:function(){if(this.prevHit){var B=this.options,A=this.plotOffset,C=this.prevHit;this.octx.clearRect(this.tHoz(C.x)+A.left-B.points.radius*2,this.tVert(C.y)+A.top-B.points.radius*2,B.points.radius*3+B.points.lineWidth*3,B.points.radius*3+B.points.lineWidth*3);this.prevHit=null}},hit:function(I){var G=this.series,C=this.options,R=this.prevHit,H=this.plotOffset,D=this.octx,S,A,M,Q,L={dist:Number.MAX_VALUE,x:null,y:null,relX:I.relX,relY:I.relY,absX:I.absX,absY:I.absY,mouse:null};for(Q=0;Q<G.length;Q++){s=G[Q];if(!s.mouse.track){continue}S=s.data;A=(s.xaxis.scale*s.mouse.sensibility);M=(s.yaxis.scale*s.mouse.sensibility);for(var P=0,B,E;P<S.length;P++){if(S[P][1]===null){continue}B=Math.pow(s.xaxis.scale*(S[P][0]-I.x),2);E=Math.pow(s.yaxis.scale*(S[P][1]-I.y),2);if(B<A&&E<M&&Math.sqrt(B+E)<L.dist){L.dist=Math.sqrt(B+E);L.x=S[P][0];L.y=S[P][1];L.mouse=s.mouse}}}if(L.mouse&&L.mouse.track&&!R||(R&&(L.x!=R.x||L.y!=R.y))){var K=this.mouseTrack||this.el.select(".flotr-mouse-value")[0],F="",J=C.mouse.position,N=C.mouse.margin,O="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";if(!C.mouse.relative){if(J.charAt(0)=="n"){F+="top:"+(N+H.top)+"px;"}else{if(J.charAt(0)=="s"){F+="bottom:"+(N+H.bottom)+"px;"}}if(J.charAt(1)=="e"){F+="right:"+(N+H.right)+"px;"}else{if(J.charAt(1)=="w"){F+="left:"+(N+H.left)+"px;"}}}else{if(J.charAt(0)=="n"){F+="bottom:"+(N-H.top-this.tVert(L.y)+this.canvasHeight)+"px;"}else{if(J.charAt(0)=="s"){F+="top:"+(N+H.top+this.tVert(L.y))+"px;"}}if(J.charAt(1)=="e"){F+="left:"+(N+H.left+this.tHoz(L.x))+"px;"}else{if(J.charAt(1)=="w"){F+="right:"+(N-H.left-this.tHoz(L.x)+this.canvasWidth)+"px;"}}}O+=F;if(!K){this.el.insert('<div class="flotr-mouse-value" style="'+O+'"></div>');K=this.mouseTrack=this.el.select(".flotr-mouse-value").first()}else{this.mouseTrack=K.setStyle(O)}if(L.x!==null&&L.y!==null){K.show();this.clearHit();if(L.mouse.lineColor!=null){D.save();D.translate(H.left,H.top);D.lineWidth=C.points.lineWidth;D.strokeStyle=L.mouse.lineColor;D.fillStyle="#ffffff";D.beginPath();D.arc(this.tHoz(L.x),this.tVert(L.y),C.mouse.radius,0,2*Math.PI,true);D.fill();D.stroke();D.restore()}this.prevHit=L;var T=L.mouse.trackDecimals;if(T==null||T<0){T=0}K.innerHTML=L.mouse.trackFormatter({x:L.x.toFixed(T),y:L.y.toFixed(T)});K.fire("flotr:hit",[L,this])}else{if(R){K.hide();this.clearHit()}}}},saveImage:function(D,C,A,B){var E=null;switch(D){case"jpeg":case"jpg":E=Canvas2Image.saveAsJPEG(this.canvas,B,C,A);break;default:case"png":E=Canvas2Image.saveAsPNG(this.canvas,B,C,A);break;case"bmp":E=Canvas2Image.saveAsBMP(this.canvas,B,C,A);break}if(Object.isElement(E)&&B){this.restoreCanvas();this.canvas.hide();this.overlay.hide();this.el.insert(E.setStyle({position:"absolute"}))}},restoreCanvas:function(){this.canvas.show();this.overlay.show();this.el.select("img").invoke("remove")}});Flotr.Color=Class.create({initialize:function(E,D,B,C){this.rgba=["r","g","b","a"];var A=4;while(-1<--A){this[this.rgba[A]]=arguments[A]||((A==3)?1:0)}this.normalize()},adjust:function(D,C,E,B){var A=4;while(-1<--A){if(arguments[A]!=null){this[this.rgba[A]]+=arguments[A]}}return this.normalize()},clone:function(){return new Flotr.Color(this.r,this.b,this.g,this.a)},limit:function(B,A,C){return Math.max(Math.min(B,C),A)},normalize:function(){var A=this.limit;this.r=A(parseInt(this.r),0,255);this.g=A(parseInt(this.g),0,255);this.b=A(parseInt(this.b),0,255);this.a=A(this.a,0,1);return this},scale:function(D,C,E,B){var A=4;while(-1<--A){if(arguments[A]!=null){this[this.rgba[A]]*=arguments[A]}}return this.normalize()},distance:function(B){if(!B){return }B=new Flotr.parseColor(B);var C=0;var A=3;while(-1<--A){C+=Math.abs(this[this.rgba[A]]-B[this.rgba[A]])}return C},toString:function(){return(this.a>=1)?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"}});Flotr.Color.lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};Flotr.Date={format:function(F,E){if(!F){return }var A=function(H){H=H.toString();return H.length==1?"0"+H:H};var D=[];var C=false;for(var B=0;B<E.length;++B){var G=E.charAt(B);if(C){switch(G){case"h":G=F.getUTCHours().toString();break;case"H":G=A(F.getUTCHours());break;case"M":G=A(F.getUTCMinutes());break;case"S":G=A(F.getUTCSeconds());break;case"d":G=F.getUTCDate().toString();break;case"m":G=(F.getUTCMonth()+1).toString();break;case"y":G=F.getUTCFullYear().toString();break;case"b":G=Flotr.Date.monthNames[F.getUTCMonth()];break}D.push(G);C=false}else{if(G=="%"){C=true}else{D.push(G)}}}return D.join("")},timeUnits:{second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000},spec:[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]};

--- /dev/null
+++ b/js/flotr/flotr.debug-0.2.0-alpha_radar1.js
@@ -1,1 +1,3349 @@
+//Flotr 0.2.0-alpha Copyright (c) 2009 Bas Wenneker, <http://solutoire.com>, MIT License.

+//

+//Radar chart added by Ryan Simmons

+//
+/* $Id: flotr.js 82 2009-01-12 19:19:31Z fabien.menager $ */

+

+var Flotr = {

+	version: '0.2.0-alpha',

+	author: 'Bas Wenneker',

+	website: 'http://www.solutoire.com',

+	/**

+	 * An object of the default registered graph types. Use Flotr.register(type, functionName)

+	 * to add your own type.

+	 */

+	_registeredTypes:{

+		'lines': 'drawSeriesLines',

+		'points': 'drawSeriesPoints',

+		'bars': 'drawSeriesBars',

+		'candles': 'drawSeriesCandles',

+		'pie': 'drawSeriesPie',

+		'radar':'drawSeriesRadar'

+	},

+	/**

+	 * Can be used to register your own chart type. Default types are 'lines', 'points' and 'bars'.

+	 * This is still experimental.

+	 * @todo Test and confirm.

+	 * @param {String} type - type of chart, like 'pies', 'bars' etc.

+	 * @param {String} functionName - Name of the draw function, like 'drawSeriesPies', 'drawSeriesBars' etc.

+	 */

+	register: function(type, functionName){

+		Flotr._registeredTypes[type] = functionName+'';	

+	},

+	/**

+	 * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha.

+	 * You could also draw graphs by directly calling Flotr.Graph(element, data, options).

+	 * @param {Element} el - element to insert the graph into

+	 * @param {Object} data - an array or object of dataseries

+	 * @param {Object} options - an object containing options

+	 * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph

+	 * @return {Class} returns a new graph object and of course draws the graph.

+	 */

+	draw: function(el, data, options, _GraphKlass_){	

+		_GraphKlass_ = _GraphKlass_ || Flotr.Graph;

+		return new _GraphKlass_(el, data, options);

+	},

+	/**

+	 * Collects dataseries from input and parses the series into the right format. It returns an Array 

+	 * of Objects each having at least the 'data' key set.

+	 * @param {Array/Object} data - Object or array of dataseries

+	 * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)})

+	 */

+	getSeries: function(data){

+		return data.collect(function(serie){

+			var i, serie = (serie.data) ? Object.clone(serie) : {'data': serie};

+			for (i = serie.data.length-1; i > -1; --i) {

+				serie.data[i][1] = (serie.data[i][1] === null ? null : parseFloat(serie.data[i][1])); 

+			}

+			return serie;

+		});

+	},

+	/**

+	 * Recursively merges two objects.

+	 * @param {Object} src - source object (likely the object with the least properties)

+	 * @param {Object} dest - destination object (optional, object with the most properties)

+	 * @return {Object} recursively merged Object

+	 */

+	merge: function(src, dest){

+		var result = dest || {};

+		for(var i in src){

+			result[i] = (src[i] != null && typeof(src[i]) == 'object' && !(src[i].constructor == Array || src[i].constructor == RegExp) && !Object.isElement(src[i])) ? Flotr.merge(src[i], dest[i]) : result[i] = src[i];		

+		}

+		return result;

+	},	

+	/**

+	 * Function calculates the ticksize and returns it.

+	 * @param {Integer} noTicks - number of ticks

+	 * @param {Integer} min - lower bound integer value for the current axis

+	 * @param {Integer} max - upper bound integer value for the current axis

+	 * @param {Integer} decimals - number of decimals for the ticks

+	 * @return {Integer} returns the ticksize in pixels

+	 */

+	getTickSize: function(noTicks, min, max, decimals){

+		var delta = (max - min) / noTicks;	

+		var magn = Flotr.getMagnitude(delta);

+		

+		// Norm is between 1.0 and 10.0.

+		var norm = delta / magn;

+		

+		var tickSize = 10;

+		if(norm < 1.5) tickSize = 1;

+		else if(norm < 2.25) tickSize = 2;

+		else if(norm < 3) tickSize = ((decimals == 0) ? 2 : 2.5);

+		else if(norm < 7.5) tickSize = 5;

+		

+		return tickSize * magn;

+	},

+	/**

+	 * Default tick formatter.

+	 * @param {String/Integer} val - tick value integer

+	 * @return {String} formatted tick string

+	 */

+	defaultTickFormatter: function(val){

+		return val+'';

+	},

+	/**

+	 * Formats the mouse tracker values.

+	 * @param {Object} obj - Track value Object {x:..,y:..}

+	 * @return {String} Formatted track string

+	 */

+	defaultTrackFormatter: function(obj){

+		return '('+obj.x+', '+obj.y+')';

+	}, 

+	defaultPieLabelFormatter: function(slice) {

+	  return (slice.fraction*100).toFixed(2)+'%';

+	},

+	/**

+	 * Returns the magnitude of the input value.

+	 * @param {Integer/Float} x - integer or float value

+	 * @return {Integer/Float} returns the magnitude of the input value

+	 */

+	getMagnitude: function(x){

+		return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));

+	},

+	toPixel: function(val){

+		return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val);

+	},

+	toRad: function(angle){

+		return -angle * (Math.PI/180);

+	},

+	/**

+	 * Parses a color string and returns a corresponding Color.

+	 * @param {String} str - string thats representing a color

+	 * @return {Color} returns a Color object or false

+	 */

+	parseColor: function(str){

+		if (str instanceof Flotr.Color) return str;

+		

+		var result, Color = Flotr.Color;

+

+		// rgb(num,num,num)

+		if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)))

+			return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]));

+	

+		// rgba(num,num,num,num)

+		if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))

+			return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4]));

+			

+		// rgb(num%,num%,num%)

+		if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)))

+			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);

+	

+		// rgba(num%,num%,num%,num)

+		if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))

+			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));

+			

+		// #a0b1c2

+		if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)))

+			return new Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16));

+	

+		// #fff

+		if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)))

+			return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));

+

+		// Otherwise, we're most likely dealing with a named color.

+		var name = str.strip().toLowerCase();

+		if(name == 'transparent'){

+			return new Color(255, 255, 255, 0);

+		}

+		return ((result = Color.lookupColors[name])) ? new Color(result[0], result[1], result[2]) : false;

+	},

+	/**

+	 * Extracts the background-color of the passed element.

+	 * @param {Element} element

+	 * @return {String} color string

+	 */

+	extractColor: function(element){

+		var color;

+		// Loop until we find an element with a background color and stop when we hit the body element. 

+		do {

+			color = element.getStyle('background-color').toLowerCase();

+			if(!(color == '' || color == 'transparent')) break;

+			element = element.up(0);

+		} while(!element.nodeName.match(/^body$/i));

+

+		// Catch Safari's way of signaling transparent.

+		return (color == 'rgba(0, 0, 0, 0)') ? 'transparent' : color;

+	}

+};

+/**

+ * Flotr Graph class that plots a graph on creation.

+

+ */

+Flotr.Graph = Class.create({

+	/**

+	 * Flotr Graph constructor.

+	 * @param {Element} el - element to insert the graph into

+	 * @param {Object} data - an array or object of dataseries

+ 	 * @param {Object} options - an object containing options

+	 */

+	initialize: function(el, data, options){

+		this.el = $(el);

+		

+		if (!this.el) throw 'The target container doesn\'t exist';

+		

+		this.data = data;

+		this.series = Flotr.getSeries(data);

+		this.setOptions(options);

+		

+		// Initialize some variables

+		this.lastMousePos = { pageX: null, pageY: null };

+		this.selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} };

+		this.prevSelection = null;

+		this.selectionInterval = null;

+		this.ignoreClick = false;   

+		this.prevHit = null;

+    

+		// Create and prepare canvas.

+		this.constructCanvas();

+		

+		// Add event handlers for mouse tracking, clicking and selection

+		this.initEvents();

+		

+		this.findDataRanges();

+		this.calculateTicks(this.axes.x);

+		this.calculateTicks(this.axes.x2);

+		this.calculateTicks(this.axes.y);

+		this.calculateTicks(this.axes.y2);

+		

+		this.calculateSpacing();

+		this.draw();

+		this.insertLegend();

+    

+		// Graph and Data tabs

+		if (this.options.spreadsheet.show) 

+		this.constructTabs();

+	},

+	/**

+	 * Sets options and initializes some variables and color specific values, used by the constructor. 

+	 * @param {Object} opts - options object

+	 */

+  setOptions: function(opts){

+    var options = {

+      colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.

+      title: null,

+      subtitle: null,

+      legend: {

+        show: true,            // => setting to true will show the legend, hide otherwise

+        noColumns: 1,          // => number of colums in legend table // @todo: doesn't work for HtmlText = false

+        labelFormatter: Prototype.K, // => fn: string -> string

+        labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes

+        labelBoxWidth: 14,

+        labelBoxHeight: 10,

+        labelBoxMargin: 5,

+        container: null,       // => container (as jQuery object) to put legend in, null means default on top of graph

+        position: 'nw',        // => position of default legend container within plot

+        margin: 5,             // => distance from grid edge to default legend container within plot

+        backgroundColor: null, // => null means auto-detect

+        backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background

+      },

+      xaxis: {

+        ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]

+        showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise

+        labelsAngle: 0,        // => Labels' angle, in degrees

+        title: null,           // => axis title

+        titleAngle: 0,         // => axis title's angle, in degrees

+        noTicks: 5,            // => number of ticks for automagically generated ticks

+        tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string

+        tickDecimals: null,    // => no. of decimals, null means auto

+        min: null,             // => min. value to show, null means set automatically

+        max: null,             // => max. value to show, null means set automatically

+        autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max

+        color: null

+      },

+      x2axis: {},

+      yaxis: {

+        ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]

+        showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise

+        labelsAngle: 0,        // => Labels' angle, in degrees

+        title: null,           // => axis title

+        titleAngle: 90,        // => axis title's angle, in degrees

+        noTicks: 5,            // => number of ticks for automagically generated ticks

+        tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string

+        tickDecimals: null,    // => no. of decimals, null means auto

+        min: null,             // => min. value to show, null means set automatically

+        max: null,             // => max. value to show, null means set automatically

+        autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max

+        color: null

+      },

+      y2axis: {

+      	titleAngle: 270

+      },

+      points: {

+        show: false,           // => setting to true will show points, false will hide

+        radius: 3,             // => point radius (pixels)

+        lineWidth: 2,          // => line width in pixels

+        fill: true,            // => true to fill the points with a color, false for (transparent) no fill

+        fillColor: '#FFFFFF',  // => fill color

+        fillOpacity: 0.4

+      },

+      lines: {

+        show: false,           // => setting to true will show lines, false will hide

+        lineWidth: 2,          // => line width in pixels

+        fill: false,           // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+      },

+      radar: {

+        show: false,           // => setting to true will show radar chart, false will hide

+        lineWidth: 2,          // => line width in pixels

+        fill: false,           // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+      },

+      bars: {

+        show: false,           // => setting to true will show bars, false will hide

+        lineWidth: 2,          // => in pixels

+        barWidth: 1,           // => in units of the x axis

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        horizontal: false,

+        stacked: false

+      },

+      candles: {

+        show: false,           // => setting to true will show candle sticks, false will hide

+        lineWidth: 1,          // => in pixels

+        wickLineWidth: 1,      // => in pixels

+        candleWidth: 0.6,      // => in units of the x axis

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        upFillColor: '#00A8F0',// => up sticks fill color

+        downFillColor: '#CB4B4B',// => down sticks fill color

+        fillOpacity: 0.5,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        barcharts: false       // => draw as barcharts (not standard bars but financial barcharts)

+      },

+      pie: {

+        show: false,           // => setting to true will show bars, false will hide

+        lineWidth: 1,          // => in pixels

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.6,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        explode: 6,

+        sizeRatio: 0.6,

+        startAngle: Math.PI/4,

+        labelFormatter: Flotr.defaultPieLabelFormatter,

+        pie3D: false,

+        pie3DviewAngle: (Math.PI/2 * 0.8),

+        pie3DspliceThickness: 20

+      },

+      grid: {

+        color: '#545454',      // => primary color used for outline and labels

+        backgroundColor: null, // => null for transparent, else color

+        tickColor: '#DDDDDD',  // => color used for the ticks

+        labelMargin: 3,        // => margin in pixels

+        verticalLines: true,   // => whether to show gridlines in vertical direction

+        horizontalLines: true, // => whether to show gridlines in horizontal direction

+        outlineWidth: 2        // => width of the grid outline/border in pixels

+      },

+      selection: {

+        mode: null,            // => one of null, 'x', 'y' or 'xy'

+        color: '#B6D9FF',      // => selection box color

+        fps: 20                // => frames-per-second

+      },

+      mouse: {

+        track: false,          // => true to track the mouse, no tracking otherwise

+        position: 'se',        // => position of the value box (default south-east)

+        relative: false,       // => next to the mouse cursor

+        trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box

+        margin: 5,             // => margin in pixels of the valuebox

+        lineColor: '#FF3F19',  // => line color of points that are drawn when mouse comes near a value of a series

+        trackDecimals: 1,      // => decimals for the track values

+        sensibility: 2,        // => the lower this number, the more precise you have to aim to show a value

+        radius: 3              // => radius of the track point

+      },

+      radarChartMode: false, // => true to render radar grid / and setup scaling for radar chart

+      shadowSize: 4,           // => size of the 'fake' shadow

+      defaultType: 'lines',    // => default series type

+      HtmlText: true,          // => wether to draw the text using HTML or on the canvas

+      fontSize: 7.5,             // => canvas' text font size

+      spreadsheet: {

+      	show: false,           // => show the data grid using two tabs

+      	tabGraphLabel: 'Graph',

+      	tabDataLabel: 'Data',

+      	toolbarDownload: 'Download CSV', // @todo: add language support

+      	toolbarSelectAll: 'Select all'

+      }

+    }

+    

+    options.x2axis = Object.extend(Object.clone(options.xaxis), options.x2axis);

+    options.y2axis = Object.extend(Object.clone(options.yaxis), options.y2axis);

+    this.options = Flotr.merge((opts || {}), options);

+    

+    this.axes = {

+      x:  {options: this.options.xaxis,  n: 1}, 

+      x2: {options: this.options.x2axis, n: 2}, 

+      y:  {options: this.options.yaxis,  n: 1}, 

+      y2: {options: this.options.y2axis, n: 2}

+    };

+		

+		// Initialize some variables used throughout this function.

+		var assignedColors = [],

+		    colors = [],

+		    ln = this.series.length,

+		    neededColors = this.series.length,

+		    oc = this.options.colors, 

+		    usedColors = [],

+		    variation = 0,

+		    c, i, j, s, tooClose;

+

+		// Collect user-defined colors from series.

+		for(i = neededColors - 1; i > -1; --i){

+			c = this.series[i].color;

+			if(c != null){

+				--neededColors;

+				if(Object.isNumber(c)) assignedColors.push(c);

+				else usedColors.push(Flotr.parseColor(c));

+			}

+		}

+		

+		// Calculate the number of colors that need to be generated.

+		for(i = assignedColors.length - 1; i > -1; --i)

+			neededColors = Math.max(neededColors, assignedColors[i] + 1);

+

+		// Generate needed number of colors.

+		for(i = 0; colors.length < neededColors;){

+			c = (oc.length == i) ? new Flotr.Color(100, 100, 100) : Flotr.parseColor(oc[i]);

+			

+			// Make sure each serie gets a different color.

+			var sign = variation % 2 == 1 ? -1 : 1;

+			var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;

+			c.scale(factor, factor, factor);

+

+			/**

+			 * @todo if we're getting too close to something else, we should probably skip this one

+			 */

+			colors.push(c);

+			

+			if(++i >= oc.length){

+				i = 0;

+				++variation;

+			}

+		}

+	

+		// Fill the options with the generated colors.

+		for(i = 0, j = 0; i < ln; ++i){

+			s = this.series[i];

+

+			// Assign the color.

+			if(s.color == null){

+				s.color = colors[j++].toString();

+			}else if(Object.isNumber(s.color)){

+				s.color = colors[s.color].toString();

+			}

+			

+      if (!s.xaxis) s.xaxis = this.axes.x;

+           if (s.xaxis == 1) s.xaxis = this.axes.x;

+      else if (s.xaxis == 2) s.xaxis = this.axes.x2;

+  

+      if (!s.yaxis) s.yaxis = this.axes.y;

+           if (s.yaxis == 1) s.yaxis = this.axes.y;

+      else if (s.yaxis == 2) s.yaxis = this.axes.y2;

+			

+			// Apply missing options to the series.

+			s.lines   = Object.extend(Object.clone(this.options.lines), s.lines);

+			s.points  = Object.extend(Object.clone(this.options.points), s.points);

+			s.bars    = Object.extend(Object.clone(this.options.bars), s.bars);

+			s.candles = Object.extend(Object.clone(this.options.candles), s.candles);

+			s.pie     = Object.extend(Object.clone(this.options.pie), s.pie);

+			s.radar   = Object.extend(Object.clone(this.options.radar), s.radar);

+			s.mouse   = Object.extend(Object.clone(this.options.mouse), s.mouse);

+			

+			if(s.shadowSize == null) s.shadowSize = this.options.shadowSize;

+		}

+	},

+	/**

+	 * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use 

+	 * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements

+	 * are created, the elements are inserted into the container element.

+	 */

+	constructCanvas: function(){

+		var el = this.el,

+			size, c, oc;

+		

+  	this.canvas = el.select('.flotr-canvas')[0];

+		this.overlay = el.select('.flotr-overlay')[0];

+		

+		el.childElements().invoke('remove');

+

+		// For positioning labels and overlay.

+		el.setStyle({position:'relative', cursor:'default'});

+

+		this.canvasWidth = el.getWidth();

+		this.canvasHeight = el.getHeight();

+		size = {'width': this.canvasWidth, 'height': this.canvasHeight};

+

+		if(this.canvasWidth <= 0 || this.canvasHeight <= 0){

+			throw 'Invalid dimensions for plot, width = ' + this.canvasWidth + ', height = ' + this.canvasHeight;

+		}

+

+		// Insert main canvas.

+		if (!this.canvas) {

+			c = this.canvas = new Element('canvas', size);

+			c.className = 'flotr-canvas';

+			c = c.writeAttribute('style', 'position:absolute;left:0px;top:0px;');

+		} else {

+			c = this.canvas.writeAttribute(size);

+		}

+		el.insert(c);

+		

+		if(Prototype.Browser.IE){

+			c = window.G_vmlCanvasManager.initElement(c);

+		}

+		this.ctx = c.getContext('2d');

+    

+		// Insert overlay canvas for interactive features.

+		if (!this.overlay) {

+			oc = this.overlay = new Element('canvas', size);

+			oc.className = 'flotr-overlay';

+			oc = oc.writeAttribute('style', 'position:absolute;left:0px;top:0px;');

+		} else {

+			oc = this.overlay.writeAttribute(size);

+		}

+		el.insert(oc);

+		

+		if(Prototype.Browser.IE){

+			oc = window.G_vmlCanvasManager.initElement(oc);

+		}

+		this.octx = oc.getContext('2d');

+

+		// Enable text functions

+		if (window.CanvasText) {

+		  CanvasText.enable(this.ctx);

+		  CanvasText.enable(this.octx);

+		  this.textEnabled = true;

+		}

+	},

+  getTextDimensions: function(text, canvasStyle, HtmlStyle, className) {

+    if (!text) return {width:0, height:0};

+    

+    if (!this.options.HtmlText && this.textEnabled) {

+      var bounds = this.ctx.getTextBounds(text, canvasStyle);

+      return {

+        width: bounds.width+2, 

+        height: bounds.height+6

+      };

+    }

+    else {

+      var dummyDiv = this.el.insert('<div style="position:absolute;top:-10000px;'+HtmlStyle+'" class="'+className+' flotr-dummy-div">' + text + '</div>').select(".flotr-dummy-div")[0];

+      dim = dummyDiv.getDimensions();

+      dummyDiv.remove();

+      return dim;

+    }

+  },

+	loadDataGrid: function(){

+    if (this.seriesData) return this.seriesData;

+

+		var s = this.series;

+		var dg = [];

+

+    /* The data grid is a 2 dimensions array. There is a row for each X value.

+     * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)

+    **/

+		for(i = 0; i < s.length; ++i){

+			s[i].data.each(function(v) {

+				var x = v[0],

+				    y = v[1];

+				if (r = dg.find(function(row) {return row[0] == x})) {

+					r[i+1] = y;

+				}

+				else {

+					var newRow = [];

+					newRow[0] = x;

+					newRow[i+1] = y

+					dg.push(newRow);

+				}

+			});

+		}

+		

+    // The data grid is sorted by x value

+		dg = dg.sortBy(function(v) {

+			return v[0];

+		});

+		return this.seriesData = dg;

+	},

+	

+	// @todo: make a tab manager (Flotr.Tabs)

+  showTab: function(tabName, onComplete){

+    var elementsClassNames = 'canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle';

+    switch(tabName) {

+      case 'graph':

+        this.datagrid.up().hide();

+        this.el.select(elementsClassNames).invoke('show');

+        this.tabs.data.removeClassName('selected');

+        this.tabs.graph.addClassName('selected');

+      break;

+      case 'data':

+        this.constructDataGrid();

+        this.datagrid.up().show();

+        this.el.select(elementsClassNames).invoke('hide');

+        this.tabs.data.addClassName('selected');

+        this.tabs.graph.removeClassName('selected');

+      break;

+    }

+  },

+  constructTabs: function(){

+    var tabsContainer = new Element('div', {className:'flotr-tabs-group', style:'position:absolute;left:0px;top:'+this.canvasHeight+'px;width:'+this.canvasWidth+'px;'});

+    this.el.insert({bottom: tabsContainer});

+    this.tabs = {

+    	graph: new Element('div', {className:'flotr-tab selected', style:'float:left;'}).update(this.options.spreadsheet.tabGraphLabel),

+    	data: new Element('div', {className:'flotr-tab', style:'float:left;'}).update(this.options.spreadsheet.tabDataLabel)

+    }

+    

+    tabsContainer.insert(this.tabs.graph).insert(this.tabs.data);

+    

+    this.el.setStyle({height: this.canvasHeight+this.tabs.data.getHeight()+2+'px'});

+

+    this.tabs.graph.observe('click', (function() {this.showTab('graph')}).bind(this));

+    this.tabs.data.observe('click', (function() {this.showTab('data')}).bind(this));

+  },

+  

+  // @todo: make a spreadsheet manager (Flotr.Spreadsheet)

+	constructDataGrid: function(){

+    // If the data grid has already been built, nothing to do here

+    if (this.datagrid) return this.datagrid;

+    

+		var i, j, 

+        s = this.series,

+        datagrid = this.loadDataGrid();

+

+		var t = this.datagrid = new Element('table', {className:'flotr-datagrid', style:'height:100px;'});

+		var colgroup = ['<colgroup><col />'];

+		

+		// First row : series' labels

+		var html = ['<tr class="first-row">'];

+		html.push('<th>&nbsp;</th>');

+		for (i = 0; i < s.length; ++i) {

+			html.push('<th scope="col">'+(s[i].label || String.fromCharCode(65+i))+'</th>');

+			colgroup.push('<col />');

+		}

+		html.push('</tr>');

+		

+		// Data rows

+		for (j = 0; j < datagrid.length; ++j) {

+			html.push('<tr>');

+			for (i = 0; i < s.length+1; ++i) {

+        var tag = 'td';

+        var content = (datagrid[j][i] != null ? Math.round(datagrid[j][i]*100000)/100000 : '');

+        

+        if (i == 0) {

+          tag = 'th';

+          var label;

+          if(this.options.xaxis.ticks) {

+            var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == datagrid[j][i] });

+            if (tick) label = tick[1];

+          } 

+          else {

+            label = this.options.xaxis.tickFormatter(content);

+          }

+          

+          if (label) content = label;

+        }

+

+				html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>');

+			}

+			html.push('</tr>');

+		}

+		colgroup.push('</colgroup>');

+    t.update(colgroup.join('')+html.join(''));

+    

+    if (!Prototype.Browser.IE) {

+      t.select('td').each(function(td) {

+      	td.observe('mouseover', function(e){

+      		td = e.element();

+      		var siblings = td.previousSiblings();

+      		

+      		t.select('th[scope=col]')[siblings.length-1].addClassName('hover');

+      		t.select('colgroup col')[siblings.length].addClassName('hover');

+      	});

+      	

+      	td.observe('mouseout', function(){

+      		t.select('colgroup col.hover, th.hover').each(function(e){e.removeClassName('hover')});

+      	});

+      });

+    }

+    

+		var toolbar = new Element('div', {className: 'flotr-datagrid-toolbar'}).

+	    insert(new Element('button', {type:'button', className:'flotr-datagrid-toolbar-button'}).update(this.options.spreadsheet.toolbarDownload).observe('click', this.downloadCSV.bind(this))).

+	    insert(new Element('button', {type:'button', className:'flotr-datagrid-toolbar-button'}).update(this.options.spreadsheet.toolbarSelectAll).observe('click', this.selectAllData.bind(this)));

+		

+		var container = new Element('div', {className:'flotr-datagrid-container', style:'left:0px;top:0px;width:'+this.canvasWidth+'px;height:'+this.canvasHeight+'px;overflow:auto;'});

+		container.insert(toolbar);

+		t.wrap(container.hide());

+		

+		this.el.insert(container);

+    return t;

+  },

+  selectAllData: function(){

+    if (this.tabs) {

+      var selection, range, doc, win, node = this.constructDataGrid();

+  

+      this.showTab('data');

+      

+      // deferred to be able to select the table

+      (function () {

+        if ((doc = node.ownerDocument) && (win = doc.defaultView) && 

+          win.getSelection && doc.createRange && 

+          (selection = window.getSelection()) && 

+          selection.removeAllRanges) {

+           range = doc.createRange();

+           range.selectNode(node);

+           selection.removeAllRanges();

+           selection.addRange(range);

+        }

+        else if (document.body && document.body.createTextRange && 

+          (range = document.body.createTextRange())) {

+           range.moveToElementText(node);

+           range.select();

+        }

+      }).defer();

+      return true;

+    }

+    else return false;

+  },

+  downloadCSV: function(){

+    var i, csv = '"x"',

+        series = this.series,

+        dg = this.loadDataGrid();

+    

+    for (i = 0; i < series.length; ++i) {

+      csv += '%09"'+(series[i].label || String.fromCharCode(65+i))+'"'; // \t

+    }

+    csv += "%0D%0A"; // \r\n

+    

+    for (i = 0; i < dg.length; ++i) {

+      if (this.options.xaxis.ticks) {

+        var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == dg[i][0] });

+        if (tick) dg[i][0] = tick[1];

+      } else {

+        dg[i][0] = this.options.xaxis.tickFormatter(dg[i][0]);

+      }

+      csv += dg[i].join('%09')+"%0D%0A"; // \t and \r\n

+    }

+    if (Prototype.Browser.IE) {

+      csv = csv.gsub('%09', '\t').gsub('%0A', '\n').gsub('%0D', '\r');

+      window.open().document.write(csv);

+    }

+    else {

+      window.open('data:text/csv,'+csv);

+    }

+  },

+	/**

+	 * Initializes event some handlers.

+	 */

+	initEvents: function () {

+  	//@todo: maybe stopObserving with only flotr functions

+  	this.overlay.stopObserving();

+  	this.overlay.observe('mousedown', this.mouseDownHandler.bind(this));

+		this.overlay.observe('mousemove', this.mouseMoveHandler.bind(this));

+		this.overlay.observe('click', this.clickHandler.bind(this));

+	},

+	/**

+	 * Function determines the min and max values for the xaxis and yaxis.

+	 */

+	findDataRanges: function(){

+		var s = this.series, 

+		    a = this.axes;

+		

+		a.x.datamin = 0;  a.x.datamax  = 0;

+		a.x2.datamin = 0; a.x2.datamax = 0;

+		a.y.datamin = 0;  a.y.datamax  = 0;

+		a.y2.datamin = 0; a.y2.datamax = 0;

+		

+		if(s.length > 0){

+			var i, j, h, x, y, data, xaxis, yaxis;

+		

+			// Get datamin, datamax start values 

+			for(i = 0; i < s.length; ++i) {

+				data = s[i].data, 

+				xaxis = s[i].xaxis, 

+				yaxis = s[i].yaxis;

+				

+				if (data.length > 0 && !s[i].hide) {

+					if (!xaxis.used) xaxis.datamin = xaxis.datamax = data[0][0];

+					if (!yaxis.used) yaxis.datamin = yaxis.datamax = data[0][1];

+					xaxis.used = true;

+					yaxis.used = true;

+

+					for(h = data.length - 1; h > -1; --h){

+  					x = data[h][0];

+  			         if(x < xaxis.datamin) xaxis.datamin = x;

+   					else if(x > xaxis.datamax) xaxis.datamax = x;

+  			    

+  					for(j = 1; j < data[h].length; j++){

+  						y = data[h][j];

+  				         if(y < yaxis.datamin) yaxis.datamin = y;

+  	  				else if(y > yaxis.datamax) yaxis.datamax = y;

+  					}

+					}

+				}

+				if (this.options.radarChartMode) {

+					xaxis.datamin = yaxis.datamin = - yaxis.datamax;

+					xaxis.datamax = yaxis.datamax;

+					if (!this.options.radarChartSides) this.options.radarChartSides = data.length;

+				}

+			}

+		}

+		

+		this.findXAxesValues();

+		

+		this.calculateRange(a.x);

+		this.extendXRangeIfNeededByBar(a.x);

+		

+		if (a.x2.used) {

+			this.calculateRange(a.x2);

+		  this.extendXRangeIfNeededByBar(a.x2);

+		}

+		

+		this.calculateRange(a.y);

+		this.extendYRangeIfNeededByBar(a.y);

+		

+		if (a.y2.used) {

+  		this.calculateRange(a.y2);

+  		this.extendYRangeIfNeededByBar(a.y2);

+		}

+	},

+	/**

+	 * Calculates the range of an axis to apply autoscaling.

+	 */

+	calculateRange: function(axis){

+		var o = axis.options,

+		  min = o.min != null ? o.min : axis.datamin,

+			max = o.max != null ? o.max : axis.datamax,

+			margin;

+

+		if(max - min == 0.0){

+			var widen = (max == 0.0) ? 1.0 : 0.01;

+			min -= widen;

+			max += widen;

+		}

+		axis.tickSize = Flotr.getTickSize(o.noTicks, ((this.options.radarChartMode) ? 0 : min), max, o.tickDecimals);

+

+		// Autoscaling.

+		if(o.min == null){

+			// Add a margin.

+			margin = o.autoscaleMargin;

+			if(margin != 0){

+				min -= axis.tickSize * margin;

+				

+				// Make sure we don't go below zero if all values are positive.

+				if(min < 0 && axis.datamin >= 0) min = 0;

+				min = axis.tickSize * Math.floor(min / axis.tickSize);

+			}

+		}

+		if(o.max == null){

+			margin = o.autoscaleMargin;

+			if(margin != 0){

+				max += axis.tickSize * margin;

+				if(max > 0 && axis.datamax <= 0) max = 0;				

+				max = axis.tickSize * Math.ceil(max / axis.tickSize);

+			}

+		}

+		axis.min = min;

+		axis.max = max;

+	},

+	/**

+	 * Bar series autoscaling in x direction.

+	 */

+	extendXRangeIfNeededByBar: function(axis){

+		if(axis.options.max == null){

+			var newmax = axis.max,

+			    i, s, b, c,

+			    stackedSums = [], 

+			    lastSerie = null;

+

+			for(i = 0; i < this.series.length; ++i){

+				s = this.series[i];

+				b = s.bars;

+				c = s.candles;

+				if(s.axis == axis && (b.show || c.show)) {

+					if (!b.horizontal && (b.barWidth + axis.datamax > newmax) || (c.candleWidth + axis.datamax > newmax)){

+						newmax = axis.max + s.bars.barWidth;

+					}

+					if(b.stacked && b.horizontal){

+						for (j = 0; j < s.data.length; j++) {

+							if (s.bars.show && s.bars.stacked) {

+								var x = s.data[j][0];

+								stackedSums[x] = (stackedSums[x] || 0) + s.data[j][1];

+								lastSerie = s;

+							}

+						}

+				    

+						for (j = 0; j < stackedSums.length; j++) {

+				    	newmax = Math.max(stackedSums[j], newmax);

+						}

+					}

+				}

+			}

+			axis.lastSerie = lastSerie;

+			axis.max = newmax;

+		}

+	},

+	/**

+	 * Bar series autoscaling in y direction.

+	 */

+	extendYRangeIfNeededByBar: function(axis){

+		if(axis.options.max == null){

+			var newmax = axis.max,

+				  i, s, b, c,

+				  stackedSums = [],

+				  lastSerie = null;

+									

+			for(i = 0; i < this.series.length; ++i){

+				s = this.series[i];

+				b = s.bars;

+				c = s.candles;

+				if (s.yaxis == axis && b.show && !s.hide) {

+					if (b.horizontal && (b.barWidth + axis.datamax > newmax) || (c.candleWidth + axis.datamax > newmax)){

+						newmax = axis.max + b.barWidth;

+					}

+					if(b.stacked && !b.horizontal){

+						for (j = 0; j < s.data.length; j++) {

+							if (s.bars.show && s.bars.stacked) {

+								var x = s.data[j][0];

+								stackedSums[x] = (stackedSums[x] || 0) + s.data[j][1];

+								lastSerie = s;

+							}

+						}

+						

+						for (j = 0; j < stackedSums.length; j++) {

+							newmax = Math.max(stackedSums[j], newmax);

+						}

+					}

+				}

+			}

+			axis.lastSerie = lastSerie;

+			axis.max = newmax;

+		}

+	},

+	/** 

+	 * Find every values of the x axes

+	 */

+	findXAxesValues: function(){

+		for(i = this.series.length-1; i > -1 ; --i){

+			s = this.series[i];

+			s.xaxis.values = s.xaxis.values || [];

+			for (j = s.data.length-1; j > -1 ; --j){

+				s.xaxis.values[s.data[j][0]] = {};

+			}

+		}

+	},

+	/**

+	 * Calculate axis ticks.

+	 * @param {Object} axis - axis object

+	 * @param {Object} o - axis options

+	 */

+	calculateTicks: function(axis){

+		var o = axis.options, i, v;

+		

+		axis.ticks = [];	

+		if(o.ticks){

+			var ticks = o.ticks, t, label;

+

+			if(Object.isFunction(ticks)){

+				ticks = ticks({min: axis.min, max: axis.max});

+			}

+			

+			// Clean up the user-supplied ticks, copy them over.

+			for(i = 0; i < ticks.length; ++i){

+				t = ticks[i];

+				if(typeof(t) == 'object'){

+					v = t[0];

+					label = (t.length > 1) ? t[1] : o.tickFormatter(v);

+				}else{

+					v = t;

+					label = o.tickFormatter(v);

+				}

+				axis.ticks[i] = { v: v, label: label };

+			}

+		}

+    else {

+			// Round to nearest multiple of tick size.

+			var start = axis.tickSize * Math.ceil(axis.min / axis.tickSize),

+				  decimals;

+			

+			// Then store all possible ticks.

+			for(i = 0; start + i * axis.tickSize <= axis.max; ++i){

+				v = start + i * axis.tickSize;

+				

+				// Round (this is always needed to fix numerical instability).

+				decimals = o.tickDecimals;

+				if(decimals == null) decimals = 1 - Math.floor(Math.log(axis.tickSize) / Math.LN10);

+				if(decimals < 0) decimals = 0;

+				

+				v = v.toFixed(decimals);

+				axis.ticks.push({ v: v, label: o.tickFormatter(v) });

+			}

+		}

+	},

+	/**

+	 * Calculates axis label sizes.

+	 */

+	calculateSpacing: function(){

+		var a = this.axes,

+  			options = this.options,

+  			series = this.series,

+  			margin = options.grid.labelMargin,

+  			x = a.x,

+  			x2 = a.x2,

+  			y = a.y,

+  			y2 = a.y2,

+  			maxOutset = 2,

+  			i, j, l, dim;

+		

+		// Labels width and height

+		[x, x2, y, y2].each(function(axis) {

+			var maxLabel = '';

+			

+		  if (axis.options.showLabels) {

+				for(i = 0; i < axis.ticks.length; ++i){

+					l = axis.ticks[i].label.length;

+					if(l > maxLabel.length){

+						maxLabel = axis.ticks[i].label;

+					}

+				}

+	    }

+		  axis.maxLabel  = this.getTextDimensions(maxLabel, {size:options.fontSize, angle: Flotr.toRad(axis.options.labelsAngle)}, 'font-size:smaller;', 'flotr-grid-label');

+		  axis.titleSize = this.getTextDimensions(axis.options.title, {size: options.fontSize*1.2, angle: Flotr.toRad(axis.options.titleAngle)}, 'font-weight:bold;', 'flotr-axis-title');

+		}, this);

+

+    // Title height

+    dim = this.getTextDimensions(options.title, {size: options.fontSize*1.5}, 'font-size:1em;font-weight:bold;', 'flotr-title');

+    this.titleHeight = dim.height;

+    

+    // Subtitle height

+    dim = this.getTextDimensions(options.subtitle, {size: options.fontSize}, 'font-size:smaller;', 'flotr-subtitle');

+    this.subtitleHeight = dim.height;

+

+		// Grid outline line width.

+		if(options.show){

+			maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth/2);

+		}

+		for(j = 0; j < options.length; ++j){

+			if (series[j].points.show){

+				maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2);

+			}

+		}

+		

+		var p = this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0};

+		p.left = p.right = p.top = p.bottom = maxOutset;

+		

+		p.bottom += (x.options.showLabels ?  (x.maxLabel.height  + margin) : 0) + 

+		            (x.options.title ?       (x.titleSize.height + margin) : 0);

+		

+    p.top    += (x2.options.showLabels ? (x2.maxLabel.height  + margin) : 0) + 

+                (x2.options.title ?      (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + 

+		this.options.radarChartMode ? (y.options.showLabels ?  (y.maxLabel.height  + margin) : 0) : 0;

+    

+		p.left   += (y.options.showLabels ?  (y.maxLabel.width  + margin) : 0) + 

+                (y.options.title ?       (y.titleSize.width + margin) : 0);

+		

+		p.right  += (y2.options.showLabels ? (y2.maxLabel.width  + margin) : 0) + 

+                (y2.options.title ?      (y2.titleSize.width + margin) : 0) + 

+		this.options.radarChartMode ? (x.options.showLabels ?  (x.maxLabel.width  + margin) : 0) : 0;

+    

+    p.top = Math.floor(p.top); // In order the outline not to be blured

+    

+		this.plotWidth  = this.canvasWidth - p.left - p.right;

+		this.plotHeight = this.canvasHeight - p.bottom - p.top;

+		

+		x.scale  = this.plotWidth / (x.max - x.min);

+		x2.scale = this.plotWidth / (x2.max - x2.min);

+		y.scale  = this.plotHeight / (y.max - y.min);

+		y2.scale = this.plotHeight / (y2.max - y2.min);

+	},

+	/**

+	 * Draws grid, labels and series.

+	 */

+	draw: function() {

+		this.drawGrid();

+		this.drawLabels();

+    this.drawTitles();

+    

+		if(this.series.length){

+			this.el.fire('flotr:beforedraw', [this.series, this]);

+			for(var i = 0; i < this.series.length; i++){

+				if (!this.series[i].hide)

+					this.drawSeries(this.series[i]);

+			}

+		}

+		this.el.fire('flotr:afterdraw', [this.series, this]);

+	},

+	/**

+	 * Translates absolute horizontal x coordinates to relative coordinates.

+	 * @param {Integer} x - absolute integer x coordinate

+	 * @return {Integer} translated relative x coordinate

+	 */

+	tHoz: function(x, axis){

+		axis = axis || this.axes.x;

+		return (x - axis.min) * axis.scale;

+	},

+	/**

+	 * Translates absolute vertical x coordinates to relative coordinates.

+	 * @param {Integer} y - absolute integer y coordinate

+	 * @return {Integer} translated relative y coordinate

+	 */

+	tVert: function(y, axis){

+		axis = axis || this.axes.y;

+		return this.plotHeight - (y - axis.min) * axis.scale;

+	},

+	/**

+	 * Draws a grid for the graph.

+	 */

+	drawGrid: function(){

+		if (this.options.radarChartMode) { // If we are in radar chart mode call drawRadarGrid instead and exit

+			this.drawRadarGrid();

+			return;

+		}

+		var v, o = this.options,

+		    ctx = this.ctx;

+		if(o.grid.verticalLines || o.grid.horizontalLines){

+			this.el.fire('flotr:beforegrid', [this.axes.x, this.axes.y, o, this]);

+		}

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+

+		// Draw grid background, if present in options.

+		if(o.grid.backgroundColor != null){

+			ctx.fillStyle = o.grid.backgroundColor;

+			ctx.fillRect(0, 0, this.plotWidth, this.plotHeight);

+		}

+		

+		// Draw grid lines in vertical direction.

+		ctx.lineWidth = 1;

+		ctx.strokeStyle = o.grid.tickColor;

+		ctx.beginPath();

+		if(o.grid.verticalLines){

+			for(var i = 0; i < this.axes.x.ticks.length; ++i){

+				v = this.axes.x.ticks[i].v;

+				// Don't show lines on upper and lower bounds.

+				if ((v == this.axes.x.min || v == this.axes.x.max) && o.grid.outlineWidth != 0)

+					continue;

+	

+				ctx.moveTo(Math.floor(this.tHoz(v)) + ctx.lineWidth/2, 0);

+				ctx.lineTo(Math.floor(this.tHoz(v)) + ctx.lineWidth/2, this.plotHeight);

+			}

+		}

+		

+		// Draw grid lines in horizontal direction.

+		if(o.grid.horizontalLines){

+			for(var j = 0; j < this.axes.y.ticks.length; ++j){

+				v = this.axes.y.ticks[j].v;

+				// Don't show lines on upper and lower bounds.

+				if ((v == this.axes.y.min || v == this.axes.y.max) && o.grid.outlineWidth != 0)

+					continue;

+	

+				ctx.moveTo(0, Math.floor(this.tVert(v)) + ctx.lineWidth/2);

+				ctx.lineTo(this.plotWidth, Math.floor(this.tVert(v)) + ctx.lineWidth/2);

+			}

+		}

+		ctx.stroke();

+		

+		// Draw axis/grid border.

+		if(o.grid.outlineWidth != 0) {

+			ctx.lineWidth = o.grid.outlineWidth;

+			ctx.strokeStyle = o.grid.color;

+			ctx.lineJoin = 'round';

+			ctx.strokeRect(0, 0, this.plotWidth, this.plotHeight);

+		}

+		ctx.restore();

+		if(o.grid.verticalLines || o.grid.horizontalLines){

+			this.el.fire('flotr:aftergrid', [this.axes.x, this.axes.y, o, this]);

+		}

+	},

+	/**

+	 * Draws a grid for the graph.

+	 */

+	drawRadarGrid: function(){

+		

+		var v, o = this.options,

+		    ctx = this.ctx;

+		    

+		var sides = this.options.radarChartSides,

+		    degreesInRadiansForAngle = Math.PI * 2 / sides,

+		    nintyDegrees = Math.PI / 2;

+		

+		if(o.grid.verticalLines || o.grid.horizontalLines){

+			this.el.fire('flotr:beforegrid', [this.axes.x, this.axes.y, o, this]);

+		}

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+		ctx.lineJoin = 'round';

+

+		// Draw grid background, if present in options.

+		if(o.grid.backgroundColor != null){

+			ctx.fillStyle = o.grid.backgroundColor;

+			ctx.fillRect(0, 0, this.plotWidth, this.plotHeight);

+		}

+		

+		// Draw grid lines

+		var regPoly = {};

+		regPoly.xaxis = {};

+		regPoly.yaxis = {};

+		regPoly.xaxis.min = regPoly.yaxis.min = this.axes.x.min;

+		regPoly.xaxis.max = regPoly.yaxis.max = this.axes.x.max;

+		regPoly.xaxis.scale = this.plotWidth / (this.axes.x.max - this.axes.x.min);

+		regPoly.yaxis.scale = this.plotHeight / (this.axes.x.max - this.axes.x.min);

+		

+		ctx.lineWidth = 1;

+		ctx.strokeStyle = o.grid.tickColor;

+		

+		if(o.grid.horizontalLines){

+			for(var j = 0; j < this.axes.y.ticks.length; ++j){

+				v = this.axes.y.ticks[j].v;

+				if (v < 0) continue;

+				// Don't show lines on upper and lower bounds.

+				if ((v == this.axes.y.min || v == this.axes.y.max) && o.grid.outlineWidth != 0)

+					continue;

+				regPoly.data = new Array();

+				for (i = 0; i < sides; i++) {

+					angle = nintyDegrees + (degreesInRadiansForAngle * i);

+					regPoly.data[i] = [v * Math.cos(angle), v * Math.sin(angle)]

+				}

+				regPoly.data[sides] = regPoly.data[0];

+				this.plotLine(regPoly,0);

+			}

+		}

+		

+		// Draw axis/grid border.

+		if(o.grid.outlineWidth != 0) {

+			ctx.lineWidth = o.grid.outlineWidth;

+			ctx.strokeStyle = o.grid.color;

+			regPoly.data = new Array();

+			var radius = this.axes.x.max;

+			for (i = 0; i < sides; i++) {

+				angle = nintyDegrees + (degreesInRadiansForAngle * i);

+				regPoly.data[i] = [radius * Math.cos(angle), radius * Math.sin(angle)]

+				}

+				regPoly.data[sides] = regPoly.data[0];

+				this.plotLine(regPoly,0);

+		}

+		

+		ctx.lineWidth = 1;

+		ctx.strokeStyle = o.grid.tickColor;

+		ctx.beginPath();

+		

+		if(o.grid.verticalLines){

+			for(var i = 0; i < sides; ++i){

+				ctx.moveTo(Math.floor(this.tHoz(0)) + ctx.lineWidth/2, 

+						Math.floor(this.tVert(0)) + ctx.lineWidth/2);

+				ctx.lineTo(Math.floor(this.tHoz(regPoly.data[i][0])) + ctx.lineWidth/2, 

+						Math.floor(this.tVert(regPoly.data[i][1])) + ctx.lineWidth/2);

+			}

+		}

+		

+		ctx.stroke();

+		

+		ctx.restore();

+		if(o.grid.verticalLines || o.grid.horizontalLines){

+			this.el.fire('flotr:aftergrid', [this.axes.x, this.axes.y, o, this]);

+		}

+	},

+	/**

+	* Draws labels aroung radar chart

+	*/

+	drawRadarLabels:function(){

+		var ctx = this.ctx,

+			options = this.options,

+			axis = this.axes.x,

+			tick, minY = 0, maxY = 0,

+			xOffset, yOffset;

+		var style = {

+		    size: options.fontSize,

+		    adjustAlign: true

+		  };

+		style.color = axis.options.color || options.grid.color;

+		style.angle = Flotr.toRad(axis.options.labelsAngle);

+		var radius = axis.max * 1,

+		      closeTo = axis.max * 0.1,

+		      sides = this.options.radarChartSides,

+		      degreesInRadiansForAngle = Math.PI * 2 / sides,

+		      nintyDegrees = Math.PI / 2,

+		      posdata = new Array();

+		for (i = 0; i < sides; i++) {

+				angle = nintyDegrees + (degreesInRadiansForAngle * i);

+				posdata[i] = [radius * Math.cos(angle), radius * Math.sin(angle)];

+				if (minY > posdata[i][1]) minY = posdata[i][1];

+				if (maxY < posdata[i][1]) maxY = posdata[i][1];

+				}

+		for (i = 0; i < sides; i++) {

+				tick = axis.ticks[i];

+				if(!tick.label || tick.label.length == 0) continue;

+				yOffset = 0;

+				if (posdata[i][0] > 0) {

+					style.halign = 'l';

+					xOffset = options.grid.labelMargin;

+				} else {

+					style.halign = 'r';

+					xOffset = - options.grid.labelMargin;

+				}

+				style.valign = 'm';

+				

+				if ((posdata[i][1] + closeTo) >= minY && (posdata[i][1] - closeTo) <= minY) {

+					style.valign = 't' ; 

+					style.halign = 'c';

+					yOffset = options.grid.labelMargin; 

+				};

+				if (posdata[i][1] == maxY) {

+					style.valign = 'b' ; 

+					style.halign = 'c';

+					yOffset = - options.grid.labelMargin; 

+				}

+				ctx.drawText(

+					tick.label,

+					this.plotOffset.left + this.tHoz(posdata[i][0]) + xOffset, 

+					this.plotOffset.top + this.tVert(posdata[i][1]) + yOffset,

+					style

+				);

+				}

+		

+	},

+	/**

+	 * Draws labels for x and y axis.

+	 */   

+	drawLabels: function(){		

+		// Construct fixed width label boxes, which can be styled easily. 

+		var noLabels = 0, axis,

+			xBoxWidth, i, html, tick,

+			options = this.options,

+      ctx = this.ctx,

+      a = this.axes;

+		

+		for(i = 0; i < a.x.ticks.length; ++i){

+			if (a.x.ticks[i].label) {

+				++noLabels;

+			}

+		}

+		xBoxWidth = this.plotWidth / noLabels;

+    

+		if (!options.HtmlText && this.textEnabled) {

+		  var style = {

+		    size: options.fontSize,

+        adjustAlign: true

+		  };

+

+		  // Add x labels.

+		  if (options.radarChartMode) {

+			this.drawRadarLabels();} else {

+		  axis = a.x;

+		  style.color = axis.options.color || options.grid.color;

+		  for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){

+		    tick = axis.ticks[i];

+		    if(!tick.label || tick.label.length == 0) continue;

+        

+        style.angle = Flotr.toRad(axis.options.labelsAngle);

+        style.halign = 'c';

+        style.valign = 't';

+        

+		    ctx.drawText(

+		      tick.label,

+		      this.plotOffset.left + this.tHoz(tick.v, axis), 

+		      this.plotOffset.top + this.plotHeight + options.grid.labelMargin,

+		      style

+		    );

+		  }}

+		  

+		  // Add x2 labels.

+		  axis = a.x2;

+		  style.color = axis.options.color || options.grid.color;

+		  for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){

+		    tick = axis.ticks[i];

+		    if(!tick.label || tick.label.length == 0) continue;

+        

+        style.angle = Flotr.toRad(axis.options.labelsAngle);

+        style.halign = 'c';

+        style.valign = 'b';

+        

+		    ctx.drawText(

+		      tick.label,

+		      this.plotOffset.left + this.tHoz(tick.v, axis), 

+		      this.plotOffset.top + options.grid.labelMargin,

+		      style

+		    );

+		  }

+		  

+		  // Add y labels.

+		  axis = a.y;

+		  style.color = axis.options.color || options.grid.color;

+		  for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){

+		    tick = axis.ticks[i];

+		    if (!tick.label || tick.label.length == 0 || (tick.v < 0 && this.options.radarChartMode)) continue;

+        

+        style.angle = Flotr.toRad(axis.options.labelsAngle);

+        style.halign = 'r';

+        style.valign = 'm';

+        

+		    ctx.drawText(

+		      tick.label,

+		      this.plotOffset.left + (this.options.radarChartMode ? this.tHoz(0) : 0) - options.grid.labelMargin, 

+		      this.plotOffset.top + this.tVert(tick.v, axis),

+		      style

+		    );

+		  }

+		  

+		  // Add y2 labels.

+		  axis = a.y2;

+		  style.color = axis.options.color || options.grid.color;

+		  for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){

+		    tick = axis.ticks[i];

+		    if (!tick.label || tick.label.length == 0) continue;

+        

+        style.angle = Flotr.toRad(axis.options.labelsAngle);

+        style.halign = 'l';

+        style.valign = 'm';

+        

+		    ctx.drawText(

+		      tick.label,

+		      this.plotOffset.left + this.plotWidth + options.grid.labelMargin, 

+		      this.plotOffset.top + this.tVert(tick.v, axis),

+		      style

+		    );

+		    

+				ctx.save();

+				ctx.strokeStyle = style.color;

+				ctx.beginPath();

+				ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + this.tVert(tick.v, axis));

+				ctx.lineTo(this.plotOffset.left + this.plotWidth,     this.plotOffset.top + this.tVert(tick.v, axis));

+				ctx.stroke();

+				ctx.restore();

+		  }

+		} 

+		else if (a.x.options.showLabels || 

+				     a.x2.options.showLabels || 

+				     a.y.options.showLabels || 

+				     a.y2.options.showLabels) {

+			html = ['<div style="font-size:smaller;color:' + options.grid.color + ';" class="flotr-labels">'];

+			

+			// Add x labels.

+			axis = a.x;

+			if (axis.options.showLabels){

+				for(i = 0; i < axis.ticks.length; ++i){

+					tick = axis.ticks[i];

+					if(!tick.label || tick.label.length == 0) continue;

+					html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.plotHeight + options.grid.labelMargin) + 'px;left:' + (this.plotOffset.left + this.tHoz(tick.v, axis) - xBoxWidth/2) + 'px;width:' + xBoxWidth + 'px;text-align:center;'+(axis.options.color?('color:'+axis.options.color+';'):'')+'" class="flotr-grid-label">' + tick.label + '</div>');

+				}

+			}

+			

+			// Add x2 labels.

+			axis = a.x2;

+			if (axis.options.showLabels && axis.used){

+				for(i = 0; i < axis.ticks.length; ++i){

+					tick = axis.ticks[i];

+					if(!tick.label || tick.label.length == 0) continue;

+					html.push('<div style="position:absolute;top:' + (this.plotOffset.top - options.grid.labelMargin - axis.maxLabel.height) + 'px;left:' + (this.plotOffset.left + this.tHoz(tick.v, axis) - xBoxWidth/2) + 'px;width:' + xBoxWidth + 'px;text-align:center;'+(axis.options.color?('color:'+axis.options.color+';'):'')+'" class="flotr-grid-label">' + tick.label + '</div>');

+				}

+			}

+			

+			// Add y labels.

+			axis = a.y;

+			if (axis.options.showLabels){

+				for(i = 0; i < axis.ticks.length; ++i){

+					tick = axis.ticks[i];

+					if (!tick.label || tick.label.length == 0) continue;

+					html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.tVert(tick.v, axis) - axis.maxLabel.height/2) + 'px;left:0;width:' + (this.plotOffset.left - options.grid.labelMargin) + 'px;text-align:right;'+(axis.options.color?('color:'+axis.options.color+';'):'')+'" class="flotr-grid-label">' + tick.label + '</div>');

+				}

+			}

+			

+			// Add y2 labels.

+			axis = a.y2;

+			if (axis.options.showLabels && axis.used){

+				ctx.save();

+				ctx.strokeStyle = axis.options.color || options.grid.color;

+				ctx.beginPath();

+				

+				for(i = 0; i < axis.ticks.length; ++i){

+					tick = axis.ticks[i];

+					if (!tick.label || tick.label.length == 0) continue;

+					html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.tVert(tick.v, axis) - axis.maxLabel.height/2) + 'px;right:0;width:' + (this.plotOffset.right - options.grid.labelMargin) + 'px;text-align:left;'+(axis.options.color?('color:'+axis.options.color+';'):'')+'" class="flotr-grid-label">' + tick.label + '</div>');

+

+					ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + this.tVert(tick.v, axis));

+					ctx.lineTo(this.plotOffset.left + this.plotWidth,     this.plotOffset.top + this.tVert(tick.v, axis));

+				}

+				ctx.stroke();

+				ctx.restore();

+			}

+			

+			html.push('</div>');

+			this.el.insert(html.join(''));

+		}

+	},

+  /**

+   * Draws the title and the subtitle

+   */   

+  drawTitles: function(){

+    var html,

+        options = this.options,

+        margin = options.grid.labelMargin,

+        ctx = this.ctx,

+        a = this.axes;

+      

+    if (!options.HtmlText && this.textEnabled) {

+      var style = {

+        size: options.fontSize,

+        color: options.grid.color,

+        halign: 'c'

+      };

+

+      // Add subtitle

+      if (options.subtitle){

+        ctx.drawText(

+          options.subtitle,

+          this.plotOffset.left + this.plotWidth/2, 

+          this.titleHeight + this.subtitleHeight - 2,

+          style

+        );

+      }

+      

+			style.weight = 1.5;

+      style.size *= 1.5;

+      

+      // Add title

+      if (options.title){

+        ctx.drawText(

+          options.title,

+          this.plotOffset.left + this.plotWidth/2, 

+          this.titleHeight - 2,

+          style

+        );

+      }

+      

+      style.weight = 1.8;

+      style.size *= 0.8;

+      style.adjustAlign = true;

+      

+			// Add x axis title

+			if (a.x.options.title && a.x.used){

+				style.halign = 'c';

+				style.valign = 't';

+				style.angle = Flotr.toRad(a.x.options.titleAngle);

+        ctx.drawText(

+          a.x.options.title,

+          this.plotOffset.left + this.plotWidth/2, 

+          this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin,

+          style

+        );

+      }

+			

+			// Add x2 axis title

+			if (a.x2.options.title && a.x2.used){

+				style.halign = 'c';

+				style.valign = 'b';

+				style.angle = Flotr.toRad(a.x2.options.titleAngle);

+        ctx.drawText(

+          a.x2.options.title,

+          this.plotOffset.left + this.plotWidth/2, 

+          this.plotOffset.top - a.x2.maxLabel.height - 2 * margin,

+          style

+        );

+      }

+			

+			// Add y axis title

+			if (a.y.options.title && a.y.used){

+				style.halign = 'r';

+				style.valign = 'm';

+				style.angle = Flotr.toRad(a.y.options.titleAngle);

+        ctx.drawText(

+          a.y.options.title,

+          this.plotOffset.left - a.y.maxLabel.width - 2 * margin, 

+          this.plotOffset.top + this.plotHeight / 2,

+          style

+        );

+      }

+			

+			// Add y2 axis title

+			if (a.y2.options.title && a.y2.used){

+				style.halign = 'l';

+				style.valign = 'm';

+				style.angle = Flotr.toRad(a.y2.options.titleAngle);

+        ctx.drawText(

+          a.y2.options.title,

+          this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, 

+          this.plotOffset.top + this.plotHeight / 2,

+          style

+        );

+      }

+    } 

+    else {

+      html = ['<div style="color:'+options.grid.color+';" class="flotr-titles">'];

+      

+      // Add title

+      if (options.title){

+        html.push('<div style="position:absolute;top:0;left:'+this.plotOffset.left+'px;font-size:1em;font-weight:bold;text-align:center;width:'+this.plotWidth+'px;" class="flotr-title">'+options.title+'</div>');

+      }

+      

+      // Add subtitle

+      if (options.subtitle){

+        html.push('<div style="position:absolute;top:'+this.titleHeight+'px;left:'+this.plotOffset.left+'px;font-size:smaller;text-align:center;width:'+this.plotWidth+'px;" class="flotr-subtitle">'+options.subtitle+'</div>');

+      }

+      html.push('</div>');

+      

+      

+      html.push('<div class="flotr-axis-title" style="font-weight:bold;">');

+			// Add x axis title

+			if (a.x.options.title && a.x.used){

+				html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.plotHeight + options.grid.labelMargin + a.x.titleSize.height) + 'px;left:' + this.plotOffset.left + 'px;width:' + this.plotWidth + 'px;text-align:center;" class="flotr-axis-title">' + a.x.options.title + '</div>');

+			}

+			

+			// Add x2 axis title

+			if (a.x2.options.title && a.x2.used){

+				html.push('<div style="position:absolute;top:0;left:' + this.plotOffset.left + 'px;width:' + this.plotWidth + 'px;text-align:center;" class="flotr-axis-title">' + a.x2.options.title + '</div>');

+			}

+			

+			// Add y axis title

+			if (a.y.options.title && a.y.used){

+				html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2) + 'px;left:0;text-align:right;" class="flotr-axis-title">' + a.y.options.title + '</div>');

+			}

+			

+			// Add y2 axis title

+			if (a.y2.options.title && a.y2.used){

+				html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2) + 'px;right:0;text-align:right;" class="flotr-axis-title">' + a.y2.options.title + '</div>');

+			}

+			html.push('</div>');

+      

+      this.el.insert(html.join(''));

+    }

+  },

+	/**

+	 * Actually draws the graph.

+	 * @param {Object} series - series to draw

+	 */

+	drawSeries: function(series){

+		series = series || this.series;

+		

+		var drawn = false;

+		for(var type in Flotr._registeredTypes){

+			if(series[type] && series[type].show){

+				this[Flotr._registeredTypes[type]](series);

+				drawn = true;

+			}

+		}

+		

+		if(!drawn){

+			this[Flotr._registeredTypes[this.options.defaultType]](series);

+		}

+	},

+	

+	plotLine: function(series, offset){

+		var ctx = this.ctx,

+		    xa = series.xaxis,

+		    ya = series.yaxis,

+  			tHoz = this.tHoz.bind(this),

+  			tVert = this.tVert.bind(this),

+  			data = series.data;

+			

+		if(data.length < 2) return;

+

+		var prevx = tHoz(data[0][0], xa),

+		    prevy = tVert(data[0][1], ya) + offset;

+		ctx.beginPath();

+		ctx.moveTo(prevx, prevy);

+		for(var i = 0; i < data.length - 1; ++i){

+			var x1 = data[i][0],   y1 = data[i][1],

+			    x2 = data[i+1][0], y2 = data[i+1][1];

+

+      // To allow empty values

+      if (y1 === null || y2 === null) continue;

+      

+			/**

+			 * Clip with ymin.

+			 */

+			if(y1 <= y2 && y1 < ya.min){

+				/**

+				 * Line segment is outside the drawing area.

+				 */

+				if(y2 < ya.min) continue;

+				

+				/**

+				 * Compute new intersection point.

+				 */

+				x1 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y1 = ya.min;

+			}else if(y2 <= y1 && y2 < ya.min){

+				if(y1 < ya.min) continue;

+				x2 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y2 = ya.min;

+			}

+

+			/**

+			 * Clip with ymax.

+			 */ 

+			if(y1 >= y2 && y1 > ya.max) {

+				if(y2 > ya.max) continue;

+				x1 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y1 = ya.max;

+			}

+			else if(y2 >= y1 && y2 > ya.max){

+				if(y1 > ya.max) continue;

+				x2 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y2 = ya.max;

+			}

+

+			/**

+			 * Clip with xmin.

+			 */

+			if(x1 <= x2 && x1 < xa.min){

+				if(x2 < xa.min) continue;

+				y1 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x1 = xa.min;

+			}else if(x2 <= x1 && x2 < xa.min){

+				if(x1 < xa.min) continue;

+				y2 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x2 = xa.min;

+			}

+

+			/**

+			 * Clip with xmax.

+			 */

+			if(x1 >= x2 && x1 > xa.max){

+				if (x2 > xa.max) continue;

+				y1 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x1 = xa.max;

+			}else if(x2 >= x1 && x2 > xa.max){

+				if(x1 > xa.max) continue;

+				y2 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x2 = xa.max;

+			}

+

+			if(prevx != tHoz(x1, xa) || prevy != tVert(y1, ya) + offset)

+				ctx.moveTo(tHoz(x1, xa), tVert(y1, ya) + offset);

+			

+			prevx = tHoz(x2, xa);

+			prevy = tVert(y2, ya) + offset;

+			ctx.lineTo(prevx, prevy);

+		}

+		ctx.stroke();

+	},

+	/**

+	 * Function used to fill

+	 * @param {Object} data

+	 */

+	plotLineArea: function(series, offset){

+		var data = series.data;

+		if(data.length < 2) return;

+

+		var top, lastX = 0,

+			ctx = this.ctx,

+	    xa = series.xaxis,

+	    ya = series.yaxis,

+			tHoz = this.tHoz.bind(this),

+			tVert = this.tVert.bind(this),

+			bottom = Math.min(Math.max(0, ya.min), ya.max),

+			first = true;

+		

+		ctx.beginPath();

+		for(var i = 0; i < data.length - 1; ++i){

+			

+			var x1 = data[i][0], y1 = data[i][1],

+			    x2 = data[i+1][0], y2 = data[i+1][1];

+			

+			if(x1 <= x2 && x1 < xa.min){

+				if(x2 < xa.min) continue;

+				y1 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x1 = xa.min;

+			}else if(x2 <= x1 && x2 < xa.min){

+				if(x1 < xa.min) continue;

+				y2 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x2 = xa.min;

+			}

+								

+			if(x1 >= x2 && x1 > xa.max){

+				if(x2 > xa.max) continue;

+				y1 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x1 = xa.max;

+			}else if(x2 >= x1 && x2 > xa.max){

+				if (x1 > xa.max) continue;

+				y2 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x2 = xa.max;

+			}

+

+			if(first){

+				ctx.moveTo(tHoz(x1, xa), tVert(bottom, ya) + offset);

+				first = false;

+			}

+			

+			/**

+			 * Now check the case where both is outside.

+			 */

+			if(y1 >= ya.max && y2 >= ya.max){

+				ctx.lineTo(tHoz(x1, xa), tVert(ya.max, ya) + offset);

+				ctx.lineTo(tHoz(x2, xa), tVert(ya.max, ya) + offset);

+				continue;

+			}else if(y1 <= ya.min && y2 <= ya.min){

+				ctx.lineTo(tHoz(x1, xa), tVert(ya.min, ya) + offset);

+				ctx.lineTo(tHoz(x2, xa), tVert(ya.min, ya) + offset);

+				continue;

+			}

+			

+			/**

+			 * Else it's a bit more complicated, there might

+			 * be two rectangles and two triangles we need to fill

+			 * in; to find these keep track of the current x values.

+			 */

+			var x1old = x1, x2old = x2;

+			

+			/**

+			 * And clip the y values, without shortcutting.

+			 * Clip with ymin.

+			 */

+			if(y1 <= y2 && y1 < ya.min && y2 >= ya.min){

+				x1 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y1 = ya.min;

+			}else if(y2 <= y1 && y2 < ya.min && y1 >= ya.min){

+				x2 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y2 = ya.min;

+			}

+

+			/**

+			 * Clip with ymax.

+			 */

+			if(y1 >= y2 && y1 > ya.max && y2 <= ya.max){

+				x1 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y1 = ya.max;

+			}else if(y2 >= y1 && y2 > ya.max && y1 <= ya.max){

+				x2 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y2 = ya.max;

+			}

+

+			/**

+			 * If the x value was changed we got a rectangle to fill.

+			 */

+			if(x1 != x1old){

+				top = (y1 <= ya.min) ? top = ya.min : ya.max;

+				ctx.lineTo(tHoz(x1old, xa), tVert(top, ya) + offset);

+				ctx.lineTo(tHoz(x1, xa), tVert(top, ya) + offset);

+			}

+		   	

+			/**

+			 * Fill the triangles.

+			 */

+			ctx.lineTo(tHoz(x1, xa), tVert(y1, ya) + offset);

+			ctx.lineTo(tHoz(x2, xa), tVert(y2, ya) + offset);

+

+			/**

+			 * Fill the other rectangle if it's there.

+			 */

+			if(x2 != x2old){

+				top = (y2 <= ya.min) ? ya.min : ya.max;

+				ctx.lineTo(tHoz(x2old, xa), tVert(top, ya) + offset);

+				ctx.lineTo(tHoz(x2, xa), tVert(top, ya) + offset);

+			}

+

+			lastX = Math.max(x2, x2old);

+		}

+		

+		ctx.lineTo(tHoz(lastX, xa), tVert(bottom, ya) + offset);

+		ctx.closePath();

+		ctx.fill();

+	},

+	/**

+	 * Function: (private) drawSeriesLines

+	 * 

+	 * Function draws lines series in the canvas element.

+	 * 

+	 * Parameters:

+	 * 		series - Series with options.lines.show = true.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSeriesLines: function(series){

+		series = series || this.series;

+		var ctx = this.ctx;

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+		ctx.lineJoin = 'round';

+

+		var lw = series.lines.lineWidth;

+		var sw = series.shadowSize;

+

+		if(sw > 0){

+			ctx.lineWidth = sw / 2;

+

+			var offset = lw/2 + ctx.lineWidth/2;

+			

+			ctx.strokeStyle = "rgba(0,0,0,0.1)";

+			this.plotLine(series, offset + sw/2);

+

+			ctx.strokeStyle = "rgba(0,0,0,0.2)";

+			this.plotLine(series, offset);

+

+			if(series.lines.fill) {

+				ctx.fillStyle = "rgba(0,0,0,0.05)";

+				this.plotLineArea(series, offset + sw/2);

+			}

+		}

+

+		ctx.lineWidth = lw;

+		ctx.strokeStyle = series.color;

+		if(series.lines.fill){

+			ctx.fillStyle = series.lines.fillColor != null ? series.lines.fillColor : Flotr.parseColor(series.color).scale(null, null, null, series.lines.fillOpacity).toString();

+			this.plotLineArea(series, 0);

+		}

+

+		this.plotLine(series, 0);

+		ctx.restore();

+	},

+	/**

+	 * Function: drawSeriesPoints

+	 * 

+	 * Function draws point series in the canvas element.

+	 * 

+	 * Parameters:

+	 * 		series - Series with options.points.show = true.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSeriesPoints: function(series) {

+		var ctx = this.ctx;

+		

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+

+		var lw = series.lines.lineWidth;

+		var sw = series.shadowSize;

+		

+		if(sw > 0){

+			ctx.lineWidth = sw / 2;

+      

+			ctx.strokeStyle = 'rgba(0,0,0,0.1)';

+			this.plotPointShadows(series, sw/2 + ctx.lineWidth/2, series.points.radius);

+

+			ctx.strokeStyle = 'rgba(0,0,0,0.2)';

+			this.plotPointShadows(series, ctx.lineWidth/2, series.points.radius);

+		}

+

+		ctx.lineWidth = series.points.lineWidth;

+		ctx.strokeStyle = series.color;

+		ctx.fillStyle = series.points.fillColor != null ? series.points.fillColor : series.color;

+		this.plotPoints(series, series.points.radius, series.points.fill);

+		ctx.restore();

+	},

+	plotPoints: function (series, radius, fill) {

+    var xa = series.xaxis,

+        ya = series.yaxis,

+		    ctx = this.ctx, i,

+		    data = series.data;

+			

+		for(i = data.length - 1; i > -1; --i){

+			var x = data[i][0], y = data[i][1];

+			if(x < xa.min || x > xa.max || y < ya.min || y > ya.max)

+				continue;

+			

+			ctx.beginPath();

+			ctx.arc(this.tHoz(x, xa), this.tVert(y, ya), radius, 0, 2 * Math.PI, true);

+			if(fill) ctx.fill();

+			ctx.stroke();

+		}

+	},

+	plotPointShadows: function(series, offset, radius){

+    var xa = series.xaxis,

+        ya = series.yaxis,

+		    ctx = this.ctx, i,

+		    data = series.data;

+			

+		for(i = data.length - 1; i > -1; --i){

+			var x = data[i][0], y = data[i][1];

+			if (x < xa.min || x > xa.max || y < ya.min || y > ya.max)

+				continue;

+			ctx.beginPath();

+			ctx.arc(this.tHoz(x, xa), this.tVert(y, ya) + offset, radius, 0, Math.PI, false);

+			ctx.stroke();

+		}

+	},

+	/**

+	 * Function: drawSeriesBars

+	 * 

+	 * Function draws bar series in the canvas element.

+	 * 

+	 * Parameters:

+	 * 		series - Series with options.bars.show = true.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSeriesBars: function(series) {

+		var ctx = this.ctx,

+			bw = series.bars.barWidth,

+			lw = Math.min(series.bars.lineWidth, bw);

+		

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+		ctx.lineJoin = 'miter';

+

+		/**

+		 * @todo linewidth not interpreted the right way.

+		 */

+		ctx.lineWidth = lw;

+		ctx.strokeStyle = series.color;

+    

+		this.plotBarsShadows(series, bw, 0, series.bars.fill);

+

+		if(series.bars.fill){

+			ctx.fillStyle = series.bars.fillColor != null ? series.bars.fillColor : Flotr.parseColor(series.color).scale(null, null, null, series.bars.fillOpacity).toString();

+		}

+    

+		this.plotBars(series, bw, 0, series.bars.fill);

+		ctx.restore();

+	},

+	plotBars: function(series, barWidth, offset, fill){

+		var data = series.data;

+		if(data.length < 1) return;

+		

+    var xa = series.xaxis,

+        ya = series.yaxis,

+  			ctx = this.ctx,

+  			tHoz = this.tHoz.bind(this),

+  			tVert = this.tVert.bind(this);

+

+		for(var i = 0; i < data.length; i++){

+			var x = data[i][0],

+			    y = data[i][1];

+			var drawLeft = true, drawTop = true, drawRight = true;

+			

+			// Stacked bars

+			var stackOffset = 0;

+			if(series.bars.stacked) {

+			  xa.values.each(function(o, v) {

+			    if (v == x) {

+			      stackOffset = o.stack || 0;

+			      o.stack = stackOffset + y;

+			    }

+			  });

+			}

+

+			// @todo: fix horizontal bars support

+			// Horizontal bars

+			if(series.bars.horizontal)

+				var left = stackOffset, right = x + stackOffset, bottom = y, top = y + barWidth;

+			else 

+				var left = x, right = x + barWidth, bottom = stackOffset, top = y + stackOffset;

+

+			if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)

+				continue;

+

+			if(left < xa.min){

+				left = xa.min;

+				drawLeft = false;

+			}

+

+			if(right > xa.max){

+				right = xa.max;

+				if (xa.lastSerie != series && series.bars.horizontal)

+					drawTop = false;

+			}

+

+			if(bottom < ya.min)

+				bottom = ya.min;

+

+			if(top > ya.max){

+				top = ya.max;

+				if (ya.lastSerie != series && !series.bars.horizontal)

+					drawTop = false;

+			}

+      

+			/**

+			 * Fill the bar.

+			 */

+			if(fill){

+				ctx.beginPath();

+				ctx.moveTo(tHoz(left, xa), tVert(bottom, ya) + offset);

+				ctx.lineTo(tHoz(left, xa), tVert(top, ya) + offset);

+				ctx.lineTo(tHoz(right, xa), tVert(top, ya) + offset);

+				ctx.lineTo(tHoz(right, xa), tVert(bottom, ya) + offset);

+				ctx.fill();

+			}

+

+			/**

+			 * Draw bar outline/border.

+			 */

+			if(series.bars.lineWidth != 0 && (drawLeft || drawRight || drawTop)){

+				ctx.beginPath();

+				ctx.moveTo(tHoz(left, xa), tVert(bottom, ya) + offset);

+				

+				ctx[drawLeft ?'lineTo':'moveTo'](tHoz(left, xa), tVert(top, ya) + offset);

+				ctx[drawTop  ?'lineTo':'moveTo'](tHoz(right, xa), tVert(top, ya) + offset);

+				ctx[drawRight?'lineTo':'moveTo'](tHoz(right, xa), tVert(bottom, ya) + offset);

+				         

+				ctx.stroke();

+			}

+		}

+	},

+  plotBarsShadows: function(series, barWidth, offset){

+		var data = series.data;

+    if(data.length < 1) return;

+    

+    var xa = series.xaxis,

+        ya = series.yaxis,

+        ctx = this.ctx,

+        tHoz = this.tHoz.bind(this),

+        tVert = this.tVert.bind(this),

+        sw = this.options.shadowSize;

+

+    for(var i = 0; i < data.length; i++){

+      var x = data[i][0],

+          y = data[i][1];

+      

+      // Stacked bars

+      var stackOffset = 0;

+			if(series.bars.stacked) {

+			  xa.values.each(function(o, v) {

+			    if (v == x) {

+			      stackOffset = o.stackShadow || 0;

+			      o.stackShadow = stackOffset + y;

+			    }

+			  });

+			}

+      

+      // Horizontal bars

+      if(series.bars.horizontal) 

+        var left = stackOffset, right = x + stackOffset, bottom = y, top = y + barWidth;

+      else 

+        var left = x, right = x + barWidth, bottom = stackOffset, top = y + stackOffset;

+

+      if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)

+        continue;

+

+      if(left < xa.min)   left = xa.min;

+      if(right > xa.max)  right = xa.max;

+      if(bottom < ya.min) bottom = ya.min;

+      if(top > ya.max)    top = ya.max;

+      

+      var width =  tHoz(right, xa)-tHoz(left, xa)-((tHoz(right, xa)+sw <= this.plotWidth) ? 0 : sw);

+      var height = Math.max(0, tVert(bottom, ya)-tVert(top, ya)-((tVert(bottom, ya)+sw <= this.plotHeight) ? 0 : sw));

+

+      ctx.fillStyle = 'rgba(0,0,0,0.05)';

+      ctx.fillRect(Math.min(tHoz(left, xa)+sw, this.plotWidth), Math.min(tVert(top, ya)+sw, this.plotWidth), width, height);

+    }

+  },

+	/**

+	 * Function: drawSeriesCandles

+	 * 

+	 * Function draws candles series in the canvas element.

+	 * 

+	 * Parameters:

+	 * 		series - Series with options.candles.show = true.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSeriesCandles: function(series) {

+		var ctx = this.ctx,

+			  bw = series.candles.candleWidth;

+		

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+		ctx.lineJoin = 'miter';

+

+		/**

+		 * @todo linewidth not interpreted the right way.

+		 */

+		ctx.lineWidth = series.candles.lineWidth;

+		this.plotCandlesShadows(series, bw/2);

+		this.plotCandles(series, bw/2);

+		

+		ctx.restore();

+	},

+	plotCandles: function(series, offset){

+		var data = series.data;

+		if(data.length < 1) return;

+		

+    var xa = series.xaxis,

+        ya = series.yaxis,

+  			ctx = this.ctx,

+  			tHoz = this.tHoz.bind(this),

+  			tVert = this.tVert.bind(this);

+

+		for(var i = 0; i < data.length; i++){

+      var d     = data[i],

+  		    x     = d[0],

+  		    open  = d[1],

+  		    high  = d[2],

+  		    low   = d[3],

+  		    close = d[4];

+

+			var left    = x,

+			    right   = x + series.candles.candleWidth,

+          bottom  = Math.max(ya.min, low),

+	        top     = Math.min(ya.max, high),

+          bottom2 = Math.max(ya.min, Math.min(open, close)),

+	        top2    = Math.min(ya.max, Math.max(open, close));

+

+			if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)

+				continue;

+

+			var color = series.candles[open>close?'downFillColor':'upFillColor'];

+			/**

+			 * Fill the candle.

+			 */

+			if(series.candles.fill && !series.candles.barcharts){

+				ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.candles.fillOpacity).toString();

+				ctx.fillRect(tHoz(left, xa), tVert(top2, ya) + offset, tHoz(right, xa) - tHoz(left, xa), tVert(bottom2, ya) - tVert(top2, ya));

+			}

+

+			/**

+			 * Draw candle outline/border, high, low.

+			 */

+			if(series.candles.lineWidth || series.candles.wickLineWidth){

+				var x, y, pixelOffset = (series.candles.wickLineWidth % 2) / 2;

+

+				x = Math.floor(tHoz((left + right) / 2), xa) + pixelOffset;

+				

+			  ctx.save();

+			  ctx.strokeStyle = color;

+			  ctx.lineWidth = series.candles.wickLineWidth;

+			  ctx.lineCap = 'butt';

+			  

+				if (series.candles.barcharts) {

+					ctx.beginPath();

+					

+					ctx.moveTo(x, Math.floor(tVert(top, ya) + offset));

+					ctx.lineTo(x, Math.floor(tVert(bottom, ya) + offset));

+					

+					y = Math.floor(tVert(open, ya) + offset)+0.5;

+					ctx.moveTo(Math.floor(tHoz(left, xa))+pixelOffset, y);

+					ctx.lineTo(x, y);

+					

+					y = Math.floor(tVert(close, ya) + offset)+0.5;

+					ctx.moveTo(Math.floor(tHoz(right, xa))+pixelOffset, y);

+					ctx.lineTo(x, y);

+				} 

+				else {

+  				ctx.strokeRect(tHoz(left, xa), tVert(top2, ya) + offset, tHoz(right, xa) - tHoz(left, xa), tVert(bottom2, ya) - tVert(top2, ya));

+

+  				ctx.beginPath();

+  				ctx.moveTo(x, Math.floor(tVert(top2,    ya) + offset));

+  				ctx.lineTo(x, Math.floor(tVert(top,     ya) + offset));

+  				ctx.moveTo(x, Math.floor(tVert(bottom2, ya) + offset));

+  				ctx.lineTo(x, Math.floor(tVert(bottom,  ya) + offset));

+				}

+				

+				ctx.stroke();

+				ctx.restore();

+			}

+		}

+	},

+  plotCandlesShadows: function(series, offset){

+		var data = series.data;

+    if(data.length < 1 || series.candles.barcharts) return;

+    

+    var xa = series.xaxis,

+        ya = series.yaxis,

+        tHoz = this.tHoz.bind(this),

+        tVert = this.tVert.bind(this),

+        sw = this.options.shadowSize;

+

+    for(var i = 0; i < data.length; i++){

+      var d     = data[i],

+      		x     = d[0],

+	        open  = d[1],

+	        high  = d[2],

+	        low   = d[3],

+	        close = d[4];

+      

+			var left   = x,

+	        right  = x + series.candles.candleWidth,

+          bottom = Math.max(ya.min, Math.min(open, close)),

+	        top    = Math.min(ya.max, Math.max(open, close));

+

+      if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)

+        continue;

+

+      var width =  tHoz(right, xa)-tHoz(left, xa)-((tHoz(right, xa)+sw <= this.plotWidth) ? 0 : sw);

+      var height = Math.max(0, tVert(bottom, ya)-tVert(top, ya)-((tVert(bottom, ya)+sw <= this.plotHeight) ? 0 : sw));

+

+      this.ctx.fillStyle = 'rgba(0,0,0,0.05)';

+      this.ctx.fillRect(Math.min(tHoz(left, xa)+sw, this.plotWidth), Math.min(tVert(top, ya)+sw, this.plotWidth), width, height);

+    }

+  },

+  /**

+   * Function: drawSeriesRadar

+   * 

+   * Function draws a radar chart on the canvas element.

+   * 

+   * Parameters:

+   *    series - Series with options.radar.show = true.

+   * 

+   * Returns:

+   *    void

+   */

+  drawSeriesRadar: function(series) {

+	var ctx = this.ctx,

+		options = this.options, sides= series.data.length;

+		

+	var degreesInRadiansForAngle = Math.PI * 2 / sides,

+	      nintyDegrees = Math.PI / 2;

+	

+	var poly = {};

+	

+	/* 

+	Draw radar grid

+	

+	poly.xaxis = series.xaxis;

+	poly.yaxis = series.yaxis;

+	ctx.save();

+	ctx.translate(this.plotOffset.left, this.plotOffset.top);

+	ctx.lineJoin = 'round';

+	for (radius = 20; radius <= 100; radius += 20) {

+	poly.data = new Array();

+	for (i = 0; i < sides; i++) {

+		angle = nintyDegrees + (degreesInRadiansForAngle * i);

+		poly.data[i] = [radius * Math.cos(angle), radius * Math.sin(angle)]

+	}

+	poly.data[sides] = poly.data[0];

+	this.plotLine(poly,0);}

+	

+	var outside = poly.data;

+	for (i = 0; i < sides; i++) {

+		poly.data = new Array();

+		poly.data[0] = [0,0];

+		poly.data[1] = outside[i];

+		this.plotLine(poly,0);

+	}

+	*/

+	

+	/*

+	Convert Series data into X, Y co-ordinates

+	*/

+	if (!series.dataInRadarFormat) {

+	poly.data = new Array();

+	for (i = 0; i < sides; i++) {

+		angle = nintyDegrees + (degreesInRadiansForAngle * i);

+		poly.data[i] = [series.data[i][1] * Math.cos(angle), series.data[i][1] * Math.sin(angle), series.data[i][0], series.data[i][1]]

+	}

+	poly.data[sides] = poly.data[0];

+	series.data = poly.data;

+	series.lines = series.radar;

+	series.lines.show = false;

+	series.dataInRadarFormat = true;

+	}

+	

+	this.drawSeriesLines(series);

+	

+},

+  

+  

+  /**

+   * Function: drawSeriesPie

+   * 

+   * Function draws a pie in the canvas element.

+   * 

+   * Parameters:

+   *    series - Series with options.pie.show = true.

+   * 

+   * Returns:

+   *    void

+   */

+  drawSeriesPie: function(series) {

+    if (!this.options.pie.drawn) {

+    var ctx = this.ctx,

+        options = this.options,

+        lw = series.pie.lineWidth,

+        sw = series.shadowSize,

+        data = series.data,

+        radius = (Math.min(this.canvasWidth, this.canvasHeight) * series.pie.sizeRatio) / 2,

+        html = [];

+    

+    var vScale = 1;//Math.cos(series.pie.viewAngle);

+    var plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale;

+    

+    var style = {

+      size: options.fontSize*1.2,

+      color: options.grid.color,

+      weight: 1.5

+    };

+    

+    var center = {

+      x: (this.canvasWidth+this.plotOffset.left)/2,

+      y: (this.canvasHeight-this.plotOffset.bottom)/2

+    };

+    

+    // Pie portions

+    var portions = this.series.collect(function(hash, index){

+    	if (hash.pie.show)

+      return {

+        name: (hash.label || hash.data[0][1]),

+        value: [index, hash.data[0][1]],

+        explode: hash.pie.explode

+      };

+    });

+    

+    // Sum of the portions' angles

+    var sum = portions.pluck('value').pluck(1).inject(0, function(acc, n) { return acc + n; });

+    

+    var fraction = 0.0,

+        angle = series.pie.startAngle,

+        value = 0.0;

+    

+    var slices = portions.collect(function(slice){

+      angle += fraction;

+      value = parseFloat(slice.value[1]); // @warning : won't support null values !!

+      fraction = value/sum;

+      return {

+        name:     slice.name,

+        fraction: fraction,

+        x:        slice.value[0],

+        y:        value,

+        explode:  slice.explode,

+        startAngle: 2 * angle * Math.PI,

+        endAngle:   2 * (angle + fraction) * Math.PI

+      };

+    });

+    

+    ctx.save();

+

+    if(sw > 0){

+	    slices.each(function (slice) {

+        var bisection = (slice.startAngle + slice.endAngle) / 2;

+        

+        var xOffset = center.x + Math.cos(bisection) * slice.explode + sw;

+        var yOffset = center.y + Math.sin(bisection) * slice.explode + sw;

+        

+		    this.plotSlice(xOffset, yOffset, radius, slice.startAngle, slice.endAngle, false, vScale);

+

+        ctx.fillStyle = 'rgba(0,0,0,0.1)';

+        ctx.fill();

+      }, this);

+    }

+    

+    if (options.HtmlText) {

+      html = ['<div style="color:' + this.options.grid.color + '" class="flotr-labels">'];

+    }

+    

+    slices.each(function (slice, index) {

+      var bisection = (slice.startAngle + slice.endAngle) / 2;

+      var color = options.colors[index];

+      

+      var xOffset = center.x + Math.cos(bisection) * slice.explode;

+      var yOffset = center.y + Math.sin(bisection) * slice.explode;

+      

+      this.plotSlice(xOffset, yOffset, radius, slice.startAngle, slice.endAngle, false, vScale);

+      

+      if(series.pie.fill){

+        ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.pie.fillOpacity).toString();

+        ctx.fill();

+      }

+      ctx.lineWidth = lw;

+      ctx.strokeStyle = color;

+      ctx.stroke();

+      

+      /*ctx.save();

+      ctx.scale(1, vScale);

+      

+      ctx.moveTo(xOffset, yOffset);

+      ctx.beginPath();

+      ctx.lineTo(xOffset, yOffset+plotTickness);

+      ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius+plotTickness);

+      ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius);

+      ctx.lineTo(xOffset, yOffset);

+      ctx.closePath();

+      ctx.fill();ctx.stroke();

+      

+      ctx.moveTo(xOffset, yOffset);

+      ctx.beginPath();

+      ctx.lineTo(xOffset, yOffset+plotTickness);

+      ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius+plotTickness);

+      ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius);

+      ctx.lineTo(xOffset, yOffset);

+      ctx.closePath();

+      ctx.fill();ctx.stroke();

+      

+      ctx.moveTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius);

+      ctx.beginPath();

+      ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius+plotTickness);

+      ctx.arc(xOffset, yOffset+plotTickness, radius, slice.startAngle, slice.endAngle, false);

+      ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius);

+      ctx.arc(xOffset, yOffset, radius, slice.endAngle, slice.startAngle, true);

+      ctx.closePath();

+      ctx.fill();ctx.stroke();

+      

+      ctx.scale(1, 1/vScale);

+      this.plotSlice(xOffset, yOffset+plotTickness, radius, slice.startAngle, slice.endAngle, false, vScale);

+      ctx.stroke();

+      if(series.pie.fill){

+        ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.pie.fillOpacity).toString();

+        ctx.fill();

+      }

+      

+      ctx.restore();*/

+      

+      var label = options.pie.labelFormatter(slice);

+      

+      var textAlignRight = (Math.cos(bisection) < 0);

+      var distX = xOffset + Math.cos(bisection) * (series.pie.explode + radius);

+      var distY = yOffset + Math.sin(bisection) * (series.pie.explode + radius);

+      

+      if (slice.fraction && label) {

+        if (options.HtmlText) {

+          var divStyle = 'position:absolute;top:' + (distY - 5) + 'px;'; //@todo: change

+          if (textAlignRight) {

+            divStyle += 'right:'+(this.canvasWidth - distX)+'px;text-align:right;';

+          }

+          else {

+            divStyle += 'left:'+distX+'px;text-align:left;';

+          }

+          html.push('<div style="' + divStyle + '" class="flotr-grid-label">' + label + '</div>');

+        }

+        else {

+          style.halign = textAlignRight ? 'r' : 'l';

+          ctx.drawText(

+            label, 

+            distX, 

+            distY + style.size / 2, 

+            style

+          );

+        }

+      }

+    }, this);

+

+    if (options.HtmlText) {

+      html.push('</div>');    

+      this.el.insert(html.join(''));

+    }

+    

+    ctx.restore();

+    options.pie.drawn = true;

+    }

+  },

+  plotSlice: function(x, y, radius, startAngle, endAngle, fill, vScale) {

+    var ctx = this.ctx;

+    vScale = vScale || 1;

+    

+    ctx.save();

+    ctx.scale(1, vScale);

+    ctx.beginPath();

+    ctx.moveTo(x, y);

+    ctx.arc   (x, y, radius, startAngle, endAngle, fill);

+    ctx.lineTo(x, y);

+    ctx.closePath();

+    ctx.restore();

+  },

+  plotPie: function() {}, 

+	/**

+	 * Function: insertLegend

+	 * 

+	 * Function adds a legend div to the canvas container or draws it on the canvas.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	insertLegend: function(){

+		if(!this.options.legend.show)

+			return;

+			

+		var series = this.series,

+			plotOffset = this.plotOffset,

+			options = this.options,

+			fragments = [],

+			rowStarted = false, 

+			ctx = this.ctx,

+			i;

+			

+		var noLegendItems = series.findAll(function(s) {return (s.label && !s.hide)}).size();

+

+    if (noLegendItems) {

+	    if (!options.HtmlText && this.textEnabled) {

+	      var style = {

+	        size: options.fontSize*1.1,

+	        color: options.grid.color

+	      };

+	      

+	      // @todo: take css into account

+	      //var dummyDiv = this.el.insert('<div class="flotr-legend" style="position:absolute;top:-10000px;"></div>');

+	      

+	      var p = options.legend.position, 

+	          m = options.legend.margin,

+	          lbw = options.legend.labelBoxWidth,

+	          lbh = options.legend.labelBoxHeight,

+	          lbm = options.legend.labelBoxMargin,

+	          offsetX = plotOffset.left + m,

+	          offsetY = plotOffset.top + m;

+	      

+	      // We calculate the labels' max width

+	      var labelMaxWidth = 0;

+	      for(i = series.length - 1; i > -1; --i){

+	        if(!series[i].label || series[i].hide) continue;

+	        var label = options.legend.labelFormatter(series[i].label);	

+	        labelMaxWidth = Math.max(labelMaxWidth, ctx.measureText(label, style));

+	      }

+	      

+	      var legendWidth  = Math.round(lbw + lbm*3 + labelMaxWidth),

+	          legendHeight = Math.round(noLegendItems*(lbm+lbh) + lbm);

+	

+	      if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight);

+	      if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth);

+

+	      // Legend box

+	      var color = Flotr.parseColor(options.legend.backgroundColor || 'rgb(240,240,240)').scale(null, null, null, options.legend.backgroundOpacity || 0.1).toString();

+	      

+	      ctx.fillStyle = color;

+	      ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight);

+	      ctx.strokeStyle = options.legend.labelBoxBorderColor;

+	      ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight);

+	      

+	      // Legend labels

+	      var x = offsetX + lbm;

+	      var y = offsetY + lbm;

+	      for(i = 0; i < series.length; i++){

+	        if(!series[i].label || series[i].hide) continue;

+	        var label = options.legend.labelFormatter(series[i].label);

+

+	        ctx.fillStyle = series[i].color;

+	        ctx.fillRect(x, y, lbw-1, lbh-1);

+	        

+	        ctx.strokeStyle = options.legend.labelBoxBorderColor;

+	        ctx.lineWidth = 1;

+	        ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2);

+	        

+	        // Legend text

+	        ctx.drawText(

+	          label,

+	          x + lbw + lbm,

+	          y + (lbh + style.size - ctx.fontDescent(style))/2,

+	          style

+	        );

+	        

+	        y += lbh + lbm;

+	      }

+	    }

+	    else {

+	  		for(i = 0; i < series.length; ++i){

+	  			if(!series[i].label || series[i].hide) continue;

+	  			

+	  			if(i % options.legend.noColumns == 0){

+	  				fragments.push(rowStarted ? '</tr><tr>' : '<tr>');

+	  				rowStarted = true;

+	  			}

+	  

+	  			var label = options.legend.labelFormatter(series[i].label);

+	  			

+	  			fragments.push('<td class="flotr-legend-color-box"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:' + options.legend.labelBoxWidth + 'px;height:' + options.legend.labelBoxHeight + 'px;background-color:' + series[i].color + '"></div></div></td>' +

+	  				'<td class="flotr-legend-label">' + label + '</td>');

+	  		}

+	  		if(rowStarted) fragments.push('</tr>');

+	  		

+	  		if(fragments.length > 0){

+	  			var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';

+	  			if(options.legend.container != null){

+	  				$(options.legend.container).update(table);

+	  			}else{

+	  				var pos = '';

+	  				var p = options.legend.position, m = options.legend.margin;

+	  				

+	  				     if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;';

+	  				else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;';					

+	  				     if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;';

+	  				else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;';

+	  				     

+	  				var div = this.el.insert('<div class="flotr-legend" style="position:absolute;z-index:2;' + pos +'">' + table + '</div>').select('div.flotr-legend').first();

+	  				

+	  				if(options.legend.backgroundOpacity != 0.0){

+	  					/**

+	  					 * Put in the transparent background separately to avoid blended labels and

+	  					 * label boxes.

+	  					 */

+	  					var c = options.legend.backgroundColor;

+	  					if(c == null){

+	  						var tmp = (options.grid.backgroundColor != null) ? options.grid.backgroundColor : Flotr.extractColor(div);

+	  						c = Flotr.parseColor(tmp).adjust(null, null, null, 1).toString();

+	  					}

+	  					this.el.insert('<div class="flotr-legend-bg" style="position:absolute;width:' + div.getWidth() + 'px;height:' + div.getHeight() + 'px;' + pos +'background-color:' + c + ';"> </div>').select('div.flotr-legend-bg').first().setStyle({

+	  						'opacity': options.legend.backgroundOpacity

+	  					});						

+	  				}

+	  			}

+	  		}

+	    }

+    }

+	},

+	/**

+	 * Function: getEventPosition

+	 * 

+	 * Calculates the coordinates from a mouse event object.

+	 * 

+	 * Parameters:

+	 * 		event - Mouse Event object.

+	 * 

+	 * Returns:

+	 * 		Object with x and y coordinates of the mouse.

+	 */

+	getEventPosition: function (event){

+		var offset = this.overlay.cumulativeOffset(),

+			rx = (event.pageX - offset.left - this.plotOffset.left),

+			ry = (event.pageY - offset.top - this.plotOffset.top),

+			ax = 0, ay = 0

+			

+		if(event.pageX == null && event.clientX != null){

+			var de = document.documentElement, b = document.body;

+			ax = event.clientX + (de && de.scrollLeft || b.scrollLeft || 0);

+			ay = event.clientY + (de && de.scrollTop || b.scrollTop || 0);

+		}else{

+			ax = event.pageX;

+			ay = event.pageY;

+		}

+		

+		return {

+			x:  this.axes.x.min  + rx / this.axes.x.scale,

+			x2: this.axes.x2.min + rx / this.axes.x2.scale,

+			y:  this.axes.y.max  - ry / this.axes.y.scale,

+			y2: this.axes.y2.max - ry / this.axes.y2.scale,

+			relX: rx,

+			relY: ry,

+			absX: ax,

+			absY: ay

+		};

+	},

+	/**

+	 * Function: clickHandler

+	 * 

+	 * Handler observes the 'click' event and fires the 'flotr:click' event.

+	 * 

+	 * Parameters:

+	 * 		event - 'click' Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	clickHandler: function(event){

+		if(this.ignoreClick){

+			this.ignoreClick = false;

+			return;

+		}

+		this.el.fire('flotr:click', [this.getEventPosition(event), this]);

+	},

+	/**

+	 * Function: mouseMoveHandler

+	 * 

+	 * Handler observes mouse movement over the graph area. Fires the 

+	 * 'flotr:mousemove' event.

+	 * 

+	 * Parameters:

+	 * 		event - 'mousemove' Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	mouseMoveHandler: function(event){

+ 		var pos = this.getEventPosition(event);

+    

+		this.lastMousePos.pageX = pos.absX;

+		this.lastMousePos.pageY = pos.absY;	

+		if(this.selectionInterval == null && (this.options.mouse.track || this.series.any(function(s){return s.mouse && s.mouse.track;}))){	

+			this.hit(pos);

+		}

+    

+		this.el.fire('flotr:mousemove', [event, pos, this]);

+	},

+	/**

+	 * Function: mouseDownHandler

+	 * 

+	 * Handler observes the 'mousedown' event.

+	 * 

+	 * Parameters:

+	 * 		event - 'mousedown' Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	mouseDownHandler: function (event){

+    if(event.isRightClick()) {

+      event.stop();

+      var overlay = this.overlay;

+      overlay.hide();

+      

+      function cancelContextMenu () {

+        overlay.show();

+        $(document).stopObserving('mousemove', cancelContextMenu);

+      }

+      $(document).observe('mousemove', cancelContextMenu);

+      return;

+    }

+    

+		if(!this.options.selection.mode || !event.isLeftClick()) return;

+		

+		this.setSelectionPos(this.selection.first, event);				

+		if(this.selectionInterval != null){

+			clearInterval(this.selectionInterval);

+		}

+		this.lastMousePos.pageX = null;

+		this.selectionInterval = setInterval(this.updateSelection.bind(this), 1000/this.options.selection.fps);

+		

+		this.mouseUpHandler = this.mouseUpHandler.bind(this);

+		$(document).observe('mouseup', this.mouseUpHandler);

+	},

+	/**

+	 * Function: (private) fireSelectEvent

+	 * 

+	 * Fires the 'flotr:select' event when the user made a selection.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	fireSelectEvent: function(){

+		var a = this.axes, selection = this.selection,

+			x1 = (selection.first.x <= selection.second.x) ? selection.first.x : selection.second.x,

+			x2 = (selection.first.x <= selection.second.x) ? selection.second.x : selection.first.x,

+			y1 = (selection.first.y >= selection.second.y) ? selection.first.y : selection.second.y,

+			y2 = (selection.first.y >= selection.second.y) ? selection.second.y : selection.first.y;

+		

+		x1 = a.x.min + x1 / a.x.scale;

+		x2 = a.x.min + x2 / a.x.scale;

+		y1 = a.y.max - y1 / a.y.scale;

+		y2 = a.y.max - y2 / a.y.scale;

+

+		this.el.fire('flotr:select', [{x1:x1, y1:y1, x2:x2, y2:y2}, this]);

+	},

+	/**

+	 * Function: (private) mouseUpHandler

+	 * 

+	 * Handler observes the mouseup event for the document. 

+	 * 

+	 * Parameters:

+	 * 		event - 'mouseup' Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	mouseUpHandler: function(event){

+    $(document).stopObserving('mouseup', this.mouseUpHandler);

+    event.stop();

+    

+		if(this.selectionInterval != null){

+			clearInterval(this.selectionInterval);

+			this.selectionInterval = null;

+		}

+

+		this.setSelectionPos(this.selection.second, event);

+		this.clearSelection();

+		

+		if(this.selectionIsSane()){

+			this.drawSelection();

+			this.fireSelectEvent();

+			this.ignoreClick = true;

+		}

+	},

+	/**

+	 * Function: setSelectionPos

+	 * 

+	 * Calculates the position of the selection.

+	 * 

+	 * Parameters:

+	 * 		pos - Position object.

+	 * 		event - Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	setSelectionPos: function(pos, event) {

+		var options = this.options,

+		    offset = $(this.overlay).cumulativeOffset();

+		

+		if(options.selection.mode.indexOf('x') == -1){

+			pos.x = (pos == this.selection.first) ? 0 : this.plotWidth;			   

+		}else{

+			pos.x = event.pageX - offset.left - this.plotOffset.left;

+			pos.x = Math.min(Math.max(0, pos.x), this.plotWidth);

+		}

+

+		if (options.selection.mode.indexOf('y') == -1){

+			pos.y = (pos == this.selection.first) ? 0 : this.plotHeight;

+		}else{

+			pos.y = event.pageY - offset.top - this.plotOffset.top;

+			pos.y = Math.min(Math.max(0, pos.y), this.plotHeight);

+		}

+	},

+	/**

+	 * Function: updateSelection

+	 * 

+	 * Updates (draws) the selection box.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	updateSelection: function(){

+		if(this.lastMousePos.pageX == null) return;

+		

+		this.setSelectionPos(this.selection.second, this.lastMousePos);

+		this.clearSelection();

+		

+		if(this.selectionIsSane()) this.drawSelection();

+	},

+	/**

+	 * Function: clearSelection

+	 * 

+	 * Removes the selection box from the overlay canvas.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	clearSelection: function() {

+		if(this.prevSelection == null) return;

+			

+		var prevSelection = this.prevSelection,

+			octx = this.octx,

+			plotOffset = this.plotOffset,

+			x = Math.min(prevSelection.first.x, prevSelection.second.x),

+			y = Math.min(prevSelection.first.y, prevSelection.second.y),

+			w = Math.abs(prevSelection.second.x - prevSelection.first.x),

+			h = Math.abs(prevSelection.second.y - prevSelection.first.y);

+		

+		octx.clearRect(x + plotOffset.left - octx.lineWidth,

+		               y + plotOffset.top - octx.lineWidth,

+		               w + octx.lineWidth*2,

+		               h + octx.lineWidth*2);

+		

+		this.prevSelection = null;

+	},

+	/**

+	 * Function: setSelection

+	 * 

+	 * Allows the user the manually select an area.

+	 * 

+	 * Parameters:

+	 * 		area - Object with coordinates to select.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	setSelection: function(area){

+		var options = this.options,

+			xa = this.axes.x,

+			ya = this.axes.y,

+			vertScale = yaxis.scale,

+			hozScale = xaxis.scale,

+			selX = options.selection.mode.indexOf('x') != -1,

+			selY = options.selection.mode.indexOf('y') != -1;

+		

+		this.clearSelection();

+

+		this.selection.first.y  = selX ? 0 : (ya.max - area.y1) * vertScale;

+		this.selection.second.y = selX ? this.plotHeight : (ya.max - area.y2) * vertScale;			

+		this.selection.first.x  = selY ? 0 : (area.x1 - xa.min) * hozScale;

+		this.selection.second.x = selY ? this.plotWidth : (area.x2 - xa.min) * hozScale;

+		

+		this.drawSelection();

+		this.fireSelectEvent();

+	},

+	/**

+	 * Function: (private) drawSelection

+	 * 

+	 * Draws the selection box.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSelection: function() {

+		var prevSelection = this.prevSelection,

+			selection = this.selection,

+			octx = this.octx,

+			options = this.options,

+			plotOffset = this.plotOffset;

+		

+		if(prevSelection != null &&

+			selection.first.x == prevSelection.first.x &&

+			selection.first.y == prevSelection.first.y && 

+			selection.second.x == prevSelection.second.x &&

+			selection.second.y == prevSelection.second.y)

+			return;

+		

+		octx.strokeStyle = Flotr.parseColor(options.selection.color).scale(null, null, null, 0.8).toString();

+		octx.lineWidth = 1;

+		octx.lineJoin = 'round';

+		octx.fillStyle = Flotr.parseColor(options.selection.color).scale(null, null, null, 0.4).toString();

+

+		this.prevSelection = {

+			first: { x: selection.first.x, y: selection.first.y },

+			second: { x: selection.second.x, y: selection.second.y }

+		};

+

+		var x = Math.min(selection.first.x, selection.second.x),

+		    y = Math.min(selection.first.y, selection.second.y),

+		    w = Math.abs(selection.second.x - selection.first.x),

+		    h = Math.abs(selection.second.y - selection.first.y);

+		

+		octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h);

+		octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h);

+	},

+	/**

+	 * Function: (private) selectionIsSane

+	 * 

+	 * Determines whether or not the selection is sane and should be drawn.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		boolean - True when sane, false otherwise.

+	 */

+	selectionIsSane: function(){

+		var selection = this.selection;

+		return Math.abs(selection.second.x - selection.first.x) >= 5 &&

+		       Math.abs(selection.second.y - selection.first.y) >= 5;

+	},

+	/**

+	 * Function: clearHit

+	 * 

+	 * Removes the mouse tracking point from the overlay.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	clearHit: function(){

+		if(this.prevHit){

+			var options = this.options,

+			    plotOffset = this.plotOffset,

+			    prevHit = this.prevHit;

+					

+			this.octx.clearRect(

+				this.tHoz(prevHit.x) + plotOffset.left - options.points.radius*2,

+				this.tVert(prevHit.y) + plotOffset.top - options.points.radius*2,

+				options.points.radius*3 + options.points.lineWidth*3, 

+				options.points.radius*3 + options.points.lineWidth*3

+			);

+			this.prevHit = null;

+		}		

+	},

+	/**

+	 * Function: hit

+	 * 

+	 * Retrieves the nearest data point from the mouse cursor. If it's within

+	 * a certain range, draw a point on the overlay canvas and display the x and y

+	 * value of the data.

+	 * 

+	 * Parameters:

+	 * 		mouse - Object that holds the relative x and y coordinates of the cursor.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	hit: function(mouse){

+		var series = this.series,

+			options = this.options,

+			prevHit = this.prevHit,

+			plotOffset = this.plotOffset,

+			octx = this.octx, 

+			data, xsens, ysens,

+			/**

+			 * Nearest data element.

+			 */

+			i, n = {

+				dist:Number.MAX_VALUE,

+				x:null,

+				y:null,

+				relX:mouse.relX,

+				relY:mouse.relY,

+				absX:mouse.absX,

+				absY:mouse.absY,

+				mouse:null,

+				radarData:null

+			};

+		

+		for(i = 0; i < series.length; i++){

+			s = series[i];

+			if(!s.mouse.track) continue;

+			data = s.data;

+			xsens = (s.xaxis.scale*s.mouse.sensibility);

+			ysens = (s.yaxis.scale*s.mouse.sensibility);

+

+			for(var j = 0, xpow, ypow; j < data.length; j++){

+				if (data[j][1] === null) continue;

+				xpow = Math.pow(s.xaxis.scale*(data[j][0] - mouse.x), 2);

+				ypow = Math.pow(s.yaxis.scale*(data[j][1] - mouse.y), 2);

+				if(xpow < xsens && ypow < ysens && Math.sqrt(xpow+ypow) < n.dist){

+					n.dist = Math.sqrt(xpow+ypow);

+					n.x = data[j][0];

+					n.y = data[j][1];

+					n.radarLabel = data[j][2];

+					n.radarData = data[j][3];

+					n.mouse = s.mouse;

+				}

+			}

+		}

+		

+		if(n.mouse && n.mouse.track && !prevHit || (prevHit && (n.x != prevHit.x || n.y != prevHit.y))){

+			var mt = this.mouseTrack || this.el.select(".flotr-mouse-value")[0],

+			    pos = '', 

+			    p = options.mouse.position, 

+			    m = options.mouse.margin,

+			    elStyle = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;';

+

+			if (!options.mouse.relative) { // absolute to the canvas

+						 if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;';

+				else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;';					

+				     if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;';

+				else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;';

+			}

+			else { // relative to the mouse

+			       if(p.charAt(0) == 'n') pos += 'bottom:' + (m - plotOffset.top - this.tVert(n.y) + this.canvasHeight) + 'px;';

+				else if(p.charAt(0) == 's') pos += 'top:' + (m + plotOffset.top + this.tVert(n.y)) + 'px;';

+				     if(p.charAt(1) == 'e') pos += 'left:' + (m + plotOffset.left + this.tHoz(n.x)) + 'px;';

+				else if(p.charAt(1) == 'w') pos += 'right:' + (m - plotOffset.left - this.tHoz(n.x) + this.canvasWidth) + 'px;';

+			}

+			

+			elStyle += pos;

+				     

+			if(!mt){

+				this.el.insert('<div class="flotr-mouse-value" style="'+elStyle+'"></div>');

+				mt = this.mouseTrack = this.el.select('.flotr-mouse-value').first();

+			}

+			else {

+				this.mouseTrack = mt.setStyle(elStyle);

+			}

+			

+			if(n.x !== null && n.y !== null){

+				mt.show();

+				

+				this.clearHit();

+				if(n.mouse.lineColor != null){

+					octx.save();

+					octx.translate(plotOffset.left, plotOffset.top);

+					octx.lineWidth = options.points.lineWidth;

+					octx.strokeStyle = n.mouse.lineColor;

+					octx.fillStyle = '#ffffff';

+					octx.beginPath();

+					octx.arc(this.tHoz(n.x), this.tVert(n.y), options.mouse.radius, 0, 2 * Math.PI, true);

+					octx.fill();

+					octx.stroke();

+					octx.restore();

+				}

+				this.prevHit = n;

+				

+				var decimals = n.mouse.trackDecimals;

+				if(decimals == null || decimals < 0) decimals = 0;

+				

+				mt.innerHTML = n.mouse.trackFormatter({x: n.x.toFixed(decimals), y: n.y.toFixed(decimals), 

+												radarLabel: n.radarLabel, radarData: n.radarData.toFixed(decimals)});

+				mt.fire('flotr:hit', [n, this]);

+			}

+			else if(prevHit){

+				mt.hide();

+				this.clearHit();

+			}

+		}

+	},

+	saveImage: function (type, width, height, replaceCanvas) {

+		var image = null;

+	  switch (type) {

+	  	case 'jpeg':

+	    case 'jpg': image = Canvas2Image.saveAsJPEG(this.canvas, replaceCanvas, width, height); break;

+      default:

+      case 'png': image = Canvas2Image.saveAsPNG(this.canvas, replaceCanvas, width, height); break;

+      case 'bmp': image = Canvas2Image.saveAsBMP(this.canvas, replaceCanvas, width, height); break;

+	  }

+	  if (Object.isElement(image) && replaceCanvas) {

+	    this.restoreCanvas();

+	    this.canvas.hide();

+	    this.overlay.hide();

+	  	this.el.insert(image.setStyle({position: 'absolute'}));

+	  }

+	},

+	restoreCanvas: function() {

+    this.canvas.show();

+    this.overlay.show();

+    this.el.select('img').invoke('remove');

+	}

+});

+

+Flotr.Color = Class.create({

+	initialize: function(r, g, b, a){

+		this.rgba = ['r','g','b','a'];

+		var x = 4;

+		while(-1<--x){

+			this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);

+		}

+		this.normalize();

+	},

+	

+	adjust: function(rd, gd, bd, ad) {

+		var x = 4;

+		while(-1<--x){

+			if(arguments[x] != null)

+				this[this.rgba[x]] += arguments[x];

+		}

+		return this.normalize();

+	},

+	

+	clone: function(){

+		return new Flotr.Color(this.r, this.b, this.g, this.a);

+	},

+	

+	limit: function(val,minVal,maxVal){

+		return Math.max(Math.min(val, maxVal), minVal);

+	},

+	

+	normalize: function(){

+		var limit = this.limit;

+		this.r = limit(parseInt(this.r), 0, 255);

+		this.g = limit(parseInt(this.g), 0, 255);

+		this.b = limit(parseInt(this.b), 0, 255);

+		this.a = limit(this.a, 0, 1);

+		return this;

+	},

+	

+	scale: function(rf, gf, bf, af){

+		var x = 4;

+		while(-1<--x){

+			if(arguments[x] != null)

+				this[this.rgba[x]] *= arguments[x];

+		}

+		return this.normalize();

+	},

+	

+	distance: function(color){

+		if (!color) return;

+		color = new Flotr.parseColor(color);

+	  var dist = 0;

+		var x = 3;

+		while(-1<--x){

+			dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]);

+		}

+		return dist;

+	},

+	

+	toString: function(){

+		return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')';

+	}

+});

+

+Flotr.Color.lookupColors = {

+	aqua:[0,255,255],

+	azure:[240,255,255],

+	beige:[245,245,220],

+	black:[0,0,0],

+	blue:[0,0,255],

+	brown:[165,42,42],

+	cyan:[0,255,255],

+	darkblue:[0,0,139],

+	darkcyan:[0,139,139],

+	darkgrey:[169,169,169],

+	darkgreen:[0,100,0],

+	darkkhaki:[189,183,107],

+	darkmagenta:[139,0,139],

+	darkolivegreen:[85,107,47],

+	darkorange:[255,140,0],

+	darkorchid:[153,50,204],

+	darkred:[139,0,0],

+	darksalmon:[233,150,122],

+	darkviolet:[148,0,211],

+	fuchsia:[255,0,255],

+	gold:[255,215,0],

+	green:[0,128,0],

+	indigo:[75,0,130],

+	khaki:[240,230,140],

+	lightblue:[173,216,230],

+	lightcyan:[224,255,255],

+	lightgreen:[144,238,144],

+	lightgrey:[211,211,211],

+	lightpink:[255,182,193],

+	lightyellow:[255,255,224],

+	lime:[0,255,0],

+	magenta:[255,0,255],

+	maroon:[128,0,0],

+	navy:[0,0,128],

+	olive:[128,128,0],

+	orange:[255,165,0],

+	pink:[255,192,203],

+	purple:[128,0,128],

+	violet:[128,0,128],

+	red:[255,0,0],

+	silver:[192,192,192],

+	white:[255,255,255],

+	yellow:[255,255,0]

+};

+

+// not used yet

+Flotr.Date = {

+  format: function(d, format) {

+		if (!d) return;

+

+    var leftPad = function(n) {

+      n = n.toString();

+      return n.length == 1 ? "0" + n : n;

+    };

+    

+    var r = [];

+    var escape = false;

+    

+    for (var i = 0; i < format.length; ++i) {

+      var c = format.charAt(i);

+      

+      if (escape) {

+        switch (c) {

+	        case 'h': c = d.getUTCHours().toString(); break;

+	        case 'H': c = leftPad(d.getUTCHours()); break;

+	        case 'M': c = leftPad(d.getUTCMinutes()); break;

+	        case 'S': c = leftPad(d.getUTCSeconds()); break;

+	        case 'd': c = d.getUTCDate().toString(); break;

+	        case 'm': c = (d.getUTCMonth() + 1).toString(); break;

+	        case 'y': c = d.getUTCFullYear().toString(); break;

+	        case 'b': c = Flotr.Date.monthNames[d.getUTCMonth()]; break;

+        }

+        r.push(c);

+        escape = false;

+      }

+      else {

+        if (c == "%")

+          escape = true;

+        else

+          r.push(c);

+      }

+    }

+    return r.join("");

+  },

+  timeUnits: {

+    "second": 1000,

+    "minute": 60 * 1000,

+    "hour": 60 * 60 * 1000,

+    "day": 24 * 60 * 60 * 1000,

+    "month": 30 * 24 * 60 * 60 * 1000,

+    "year": 365.2425 * 24 * 60 * 60 * 1000

+  },

+  // the allowed tick sizes, after 1 year we use an integer algorithm

+  spec: [

+    [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], 

+    [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], 

+    [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"],

+    [1, "day"], [2, "day"], [3, "day"],

+    [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"], [3, "month"], [6, "month"],

+    [1, "year"]

+  ],

+  monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

+};

 

--- /dev/null
+++ b/js/flotr/lib/base64.js
@@ -1,1 +1,113 @@
-
+/* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>

+ * Version: 1.0

+ * LastModified: Dec 25 1999

+ * This library is free.  You can redistribute it and/or modify it.

+ */

+

+/*

+ * Interfaces:

+ * b64 = base64encode(data);

+ * data = base64decode(b64);

+ */

+

+(function() {

+

+var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

+var base64DecodeChars = [

+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,

+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,

+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,

+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,

+    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,

+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,

+    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,

+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1];

+

+function base64encode(str) {

+    var out, i, len;

+    var c1, c2, c3;

+

+    len = str.length;

+    i = 0;

+    out = "";

+    while(i < len) {

+	c1 = str.charCodeAt(i++) & 0xff;

+	if(i == len)

+	{

+	    out += base64EncodeChars.charAt(c1 >> 2);

+	    out += base64EncodeChars.charAt((c1 & 0x3) << 4);

+	    out += "==";

+	    break;

+	}

+	c2 = str.charCodeAt(i++);

+	if(i == len)

+	{

+	    out += base64EncodeChars.charAt(c1 >> 2);

+	    out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));

+	    out += base64EncodeChars.charAt((c2 & 0xF) << 2);

+	    out += "=";

+	    break;

+	}

+	c3 = str.charCodeAt(i++);

+	out += base64EncodeChars.charAt(c1 >> 2);

+	out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));

+	out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));

+	out += base64EncodeChars.charAt(c3 & 0x3F);

+    }

+    return out;

+}

+

+function base64decode(str) {

+    var c1, c2, c3, c4;

+    var i, len, out;

+

+    len = str.length;

+    i = 0;

+    out = "";

+    while(i < len) {

+	/* c1 */

+	do {

+	    c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];

+	} while(i < len && c1 == -1);

+	if(c1 == -1)

+	    break;

+

+	/* c2 */

+	do {

+	    c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];

+	} while(i < len && c2 == -1);

+	if(c2 == -1)

+	    break;

+

+	out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));

+

+	/* c3 */

+	do {

+	    c3 = str.charCodeAt(i++) & 0xff;

+	    if(c3 == 61)

+		return out;

+	    c3 = base64DecodeChars[c3];

+	} while(i < len && c3 == -1);

+	if(c3 == -1)

+	    break;

+

+	out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));

+

+	/* c4 */

+	do {

+	    c4 = str.charCodeAt(i++) & 0xff;

+	    if(c4 == 61)

+		return out;

+	    c4 = base64DecodeChars[c4];

+	} while(i < len && c4 == -1);

+	if(c4 == -1)

+	    break;

+	out += String.fromCharCode(((c3 & 0x03) << 6) | c4);

+    }

+    return out;

+}

+

+if (!window.btoa) window.btoa = base64encode;

+if (!window.atob) window.atob = base64decode;

+

+})();

--- /dev/null
+++ b/js/flotr/lib/canvas2image.js
@@ -1,1 +1,230 @@
-
+/*

+ * Canvas2Image v0.1

+ * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com

+ * MIT License [http://www.opensource.org/licenses/mit-license.php]

+ */

+

+var Canvas2Image = (function() {

+	// check if we have canvas support

+	var oCanvas = document.createElement("canvas");

+  

+	// no canvas, bail out.

+	if (!oCanvas.getContext) {

+		return {

+			saveAsBMP : function(){},

+			saveAsPNG : function(){},

+			saveAsJPEG : function(){}

+		}

+	}

+

+	var bHasImageData = !!(oCanvas.getContext("2d").getImageData);

+	var bHasDataURL = !!(oCanvas.toDataURL);

+	var bHasBase64 = !!(window.btoa);

+

+	var strDownloadMime = "image/octet-stream";

+

+	// ok, we're good

+	var readCanvasData = function(oCanvas) {

+		var iWidth = parseInt(oCanvas.width);

+		var iHeight = parseInt(oCanvas.height);

+		return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight);

+	}

+

+	// base64 encodes either a string or an array of charcodes

+	var encodeData = function(data) {

+		var strData = "";

+		if (typeof data == "string") {

+			strData = data;

+		} else {

+			var aData = data;

+			for (var i = 0; i < aData.length; i++) {

+				strData += String.fromCharCode(aData[i]);

+			}

+		}

+		return btoa(strData);

+	}

+

+	// creates a base64 encoded string containing BMP data

+	// takes an imagedata object as argument

+	var createBMP = function(oData) {

+		var aHeader = [];

+	

+		var iWidth = oData.width;

+		var iHeight = oData.height;

+

+		aHeader.push(0x42); // magic 1

+		aHeader.push(0x4D); 

+	

+		var iFileSize = iWidth*iHeight*3 + 54; // total header size = 54 bytes

+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);

+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);

+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);

+		aHeader.push(iFileSize % 256);

+

+		aHeader.push(0); // reserved

+		aHeader.push(0);

+		aHeader.push(0); // reserved

+		aHeader.push(0);

+

+		aHeader.push(54); // data offset

+		aHeader.push(0);

+		aHeader.push(0);

+		aHeader.push(0);

+

+		var aInfoHeader = [];

+		aInfoHeader.push(40); // info header size

+		aInfoHeader.push(0);

+		aInfoHeader.push(0);

+		aInfoHeader.push(0);

+

+		var iImageWidth = iWidth;

+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);

+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);

+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);

+		aInfoHeader.push(iImageWidth % 256);

+	

+		var iImageHeight = iHeight;

+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);

+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);

+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);

+		aInfoHeader.push(iImageHeight % 256);

+	

+		aInfoHeader.push(1); // num of planes

+		aInfoHeader.push(0);

+	

+		aInfoHeader.push(24); // num of bits per pixel

+		aInfoHeader.push(0);

+	

+		aInfoHeader.push(0); // compression = none

+		aInfoHeader.push(0);

+		aInfoHeader.push(0);

+		aInfoHeader.push(0);

+	

+		var iDataSize = iWidth*iHeight*3; 

+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);

+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);

+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);

+		aInfoHeader.push(iDataSize % 256); 

+	

+		for (var i = 0; i < 16; i++) {

+			aInfoHeader.push(0);	// these bytes not used

+		}

+	

+		var iPadding = (4 - ((iWidth * 3) % 4)) % 4;

+

+		var aImgData = oData.data;

+

+		var strPixelData = "";

+		var y = iHeight;

+		do {

+			var iOffsetY = iWidth*(y-1)*4;

+			var strPixelRow = "";

+			for (var x=0;x<iWidth;x++) {

+				var iOffsetX = 4*x;

+

+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+2]);

+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+1]);

+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX]);

+			}

+			for (var c=0;c<iPadding;c++) {

+				strPixelRow += String.fromCharCode(0);

+			}

+			strPixelData += strPixelRow;

+		} while (--y);

+

+		return encodeData(aHeader.concat(aInfoHeader)) + encodeData(strPixelData);

+	}

+

+	// sends the generated file to the client

+	var saveFile = function(strData) {

+    if (!window.open(strData)) {

+      document.location.href = strData;

+    }

+	}

+

+	var makeDataURI = function(strData, strMime) {

+		return "data:" + strMime + ";base64," + strData;

+	}

+

+	// generates a <img> object containing the imagedata

+	var makeImageObject = function(strSource) {

+		var oImgElement = document.createElement("img");

+		oImgElement.src = strSource;

+		return oImgElement;

+	}

+

+	var scaleCanvas = function(oCanvas, iWidth, iHeight) {

+		if (iWidth && iHeight) {

+			var oSaveCanvas = document.createElement("canvas");

+			

+			oSaveCanvas.width = iWidth;

+			oSaveCanvas.height = iHeight;

+			oSaveCanvas.style.width = iWidth+"px";

+			oSaveCanvas.style.height = iHeight+"px";

+

+			var oSaveCtx = oSaveCanvas.getContext("2d");

+

+			oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth);

+			

+			return oSaveCanvas;

+		}

+		return oCanvas;

+	}

+

+	return {

+		saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) {

+			if (!bHasDataURL) {

+				return false;

+			}

+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);

+			var strData = oScaledCanvas.toDataURL("image/png");

+			if (bReturnImg) {

+				return makeImageObject(strData);

+			} else {

+				saveFile(strData.replace("image/png", strDownloadMime));

+			}

+			return true;

+		},

+

+		saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) {

+			if (!bHasDataURL) {

+				return false;

+			}

+

+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);

+			var strMime = "image/jpeg";

+			var strData = oScaledCanvas.toDataURL(strMime);

+	

+			// check if browser actually supports jpeg by looking for the mime type in the data uri.

+			// if not, return false

+			if (strData.indexOf(strMime) != 5) {

+				return false;

+			}

+

+			if (bReturnImg) {

+				return makeImageObject(strData);

+			} else {

+				saveFile(strData.replace(strMime, strDownloadMime));

+			}

+			return true;

+		},

+

+		saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) {

+			if (!(bHasImageData && bHasBase64)) {

+				return false;

+			}

+

+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);

+

+			var oData = readCanvasData(oScaledCanvas);

+			var strImgData = createBMP(oData);

+			if (bReturnImg) {

+				return makeImageObject(makeDataURI(strImgData, "image/bmp"));

+			} else {

+				saveFile(makeDataURI(strImgData, strDownloadMime));

+			}

+			return true;

+		}

+	};

+

+})();

--- /dev/null
+++ b/js/flotr/lib/canvastext.js
@@ -1,1 +1,397 @@
-
+/**

+ * This code is released to the public domain by Jim Studt, 2007.

+ * He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/


+ * A partial support for accentuated letters as been added too.

+ */

+var CanvasText = {

+	/** The letters definition. It is a list of letters, 

+	 * with their width, and the coordinates of points compositing them.

+	 * The syntax for the points is : [x, y], null value means "pen up"

+	 */

+  letters: {

+		'\n':{ width: -1, points: [] },

+    ' ': { width: 10, points: [] },

+    '!': { width: 10, points: [[5,21],[5,7],null,[5,2],[4,1],[5,0],[6,1],[5,2]] },

+    '"': { width: 16, points: [[4,21],[4,14],null,[12,21],[12,14]] },

+    '#': { width: 21, points: [[11,25],[4,-7],null,[17,25],[10,-7],null,[4,12],[18,12],null,[3,6],[17,6]] },

+    '$': { width: 20, points: [[8,25],[8,-4],null,[12,25],[12,-4],null,[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },

+    '%': { width: 24, points: [[21,21],[3,0],null,[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],null,[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },

+    '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] },

+    '\'':{ width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },

+    '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },

+    ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },

+    '*': { width: 16, points: [[8,21],[8,9],null,[3,18],[13,12],null,[13,18],[3,12]] },

+    '+': { width: 26, points: [[13,18],[13,0],null,[4,9],[22,9]] },

+    ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },

+    '-': { width: 26, points: [[4,9],[22,9]] },

+    '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },

+    '/': { width: 22, points: [[20,25],[2,-7]] },

+    '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },

+    '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },

+    '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },

+    '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },

+    '4': { width: 20, points: [[13,21],[3,7],[18,7],null,[13,21],[13,0]] },

+    '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },

+    '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },

+    '7': { width: 20, points: [[17,21],[7,0],null,[3,21],[17,21]] },

+    '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },

+    '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },

+    ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[5,2],[4,1],[5,0],[6,1],[5,2]] },

+    ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },

+    '<': { width: 24, points: [[20,18],[4,9],[20,0]] },

+    '=': { width: 26, points: [[4,12],[22,12],null,[4,6],[22,6]] },

+    '>': { width: 24, points: [[4,18],[20,9],[4,0]] },

+    '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]] },

+    '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]] },

+    'A': { width: 18, points: [[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]] },

+    'B': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },

+    'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },

+    'D': { width: 21, points: [[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },

+    'E': { width: 19, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]] },

+    'F': { width: 18, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]] },

+    'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]] },

+    'H': { width: 22, points: [[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]] },

+    'I': { width: 8,  points: [[4,21],[4,0]] },

+    'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },

+    'K': { width: 21, points: [[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]] },

+    'L': { width: 17, points: [[4,21],[4,0],null,[4,0],[16,0]] },

+    'M': { width: 24, points: [[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]] },

+    'N': { width: 22, points: [[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]] },

+    'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },

+    'P': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },

+    'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]] },

+    'R': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]] },

+    'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },

+    'T': { width: 16, points: [[8,21],[8,0],null,[1,21],[15,21]] },

+    'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },

+    'V': { width: 18, points: [[1,21],[9,0],null,[17,21],[9,0]] },

+    'W': { width: 24, points: [[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]] },

+    'X': { width: 20, points: [[3,21],[17,0],null,[17,21],[3,0]] },

+    'Y': { width: 18, points: [[1,21],[9,11],[9,0],null,[17,21],[9,11]] },

+    'Z': { width: 20, points: [[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]] },

+    '[': { width: 14, points: [[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]] },

+    '\\':{ width: 14, points: [[0,21],[14,-3]] },

+    ']': { width: 14, points: [[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]] },

+    '^': { width: 14, points: [[3,10],[8,18],[13,10]] },

+    '_': { width: 16, points: [[0,-2],[16,-2]] },

+    '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },

+    'a': { width: 19, points: [[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'b': { width: 19, points: [[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },

+    'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'd': { width: 19, points: [[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]] },

+    'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'h': { width: 19, points: [[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },

+    'i': { width: 8,  points: [[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]] },

+    'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },

+    'k': { width: 17, points: [[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]] },

+    'l': { width: 8,  points: [[4,21],[4,0]] },

+    'm': { width: 30, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },

+    'n': { width: 19, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },

+    'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },

+    'p': { width: 19, points: [[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },

+    'q': { width: 19, points: [[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'r': { width: 13, points: [[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]] },

+    's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },

+    't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]] },

+    'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]] },

+    'v': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0]] },

+    'w': { width: 22, points: [[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]] },

+    'x': { width: 17, points: [[3,14],[14,0],null,[14,14],[3,0]] },

+    'y': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },

+    'z': { width: 17, points: [[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]] },

+    '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },

+    '|': { width: 8,  points: [[4,25],[4,-7]] },

+    '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },

+    '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] },

















+  },

+  

+  specialchars: {

+  	'pi': { width: 19, points: [[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]] }

+  },

+  

+  /** Diacritics, used to draw accentuated letters */

+  diacritics: {



+    '`': { entity: 'grave', points: [[7,22],[12,19]] },

+    '^': { entity: 'circ',  points: [[5.5,19],[9.5,23],[12.5,19]] },


+    '~': { entity: 'tilde', points: [[4,18],[7,22],[10,18],[13,22]] }

+  },

+  

+  /** The default font styling */

+  style: {

+    size: 8,          // font height in pixels

+    font: null,       // not yet implemented

+    color: '#000000', // 

+    weight: 1,        // float, 1 for 'normal'

+    halign: 'l',      // l: left, r: right, c: center

+    valign: 'b',      // t: top, m: middle, b: bottom 

+    adjustAlign: false, // modifies the alignments if the angle is different from 0 to make the spin point always at the good position

+    angle: 0,         // in radians, anticlockwise

+    tracking: 1,      // space between the letters, float, 1 for 'normal'

+    boundingBoxColor: '#ff0000', //null // color of the bounding box (null to hide), can be used for debug and font drawing

+    originPointColor: '#000000' //null // color of the bounding box (null to hide), can be used for debug and font drawing

+  },

+  

+  debug: false,

+  _bufferLexemes: {},

+  

+  /** Get the letter data corresponding to a char

+   * @param {String} ch - The char

+   */

+  letter: function(ch) {

+    return CanvasText.letters[ch];

+  },

+  

+  parseLexemes: function(str) {

+    if (CanvasText._bufferLexemes[str]) 

+      return CanvasText._bufferLexemes[str];

+    

+  	var i, c, matches = str.match(/&[A-Za-z]{2,5};|\s|./g);

+  	var result = [], chars = [];

+  	for (i = 0; i < matches.length; i++) {

+  		c = matches[i];

+  		if (c.length == 1) 

+  			chars.push(c);

+  		else {

+  			var entity = c.substring(1, c.length-1);

+  			if (CanvasText.specialchars[entity]) 

+  				chars.push(entity);

+  			else

+  				chars = chars.concat(c.toArray());

+  		}

+  	}

+  	for (i = 0; i < chars.length; i++) {

+  		c = chars[i];

+  		if (c = CanvasText.letters[c] || CanvasText.specialchars[c])

+  		  result.push(c);

+  	}

+  	return CanvasText._bufferLexemes[str] = result.compact();

+  },

+

+  /** Get the font ascent for a given style

+   * @param {Object} style - The reference style

+   */

+  ascent: function(style) {

+  	style = style || {};

+    return (style.size || CanvasText.style.size);

+  },

+  

+  /** Get the font descent for a given style 

+   * @param {Object} style - The reference style

+   * */

+  descent: function(style) {

+  	style = style || {};

+    return 7.0*(style.size || CanvasText.style.size)/25.0;

+  },

+  

+  /** Measure the text horizontal size 

+   * @param {String} str - The text

+   * @param {Object} style - Text style

+   * */

+  measure: function(str, style) {

+    if (!str) return;

+    style = style || {};

+    

+    var i, width, lexemes = CanvasText.parseLexemes(str),

+        total = 0;

+

+    for (i = lexemes.length-1; i > -1; --i) {

+    	c = lexemes[i];

+    	width = (c.diacritic) ? CanvasText.letter(c.letter).width : c.width;

+      total += width * (style.tracking || CanvasText.style.tracking) * (style.size || CanvasText.style.size) / 25.0;

+    }

+    return total;

+  },

+  

+  getDimensions: function(str, style) {

+    var width = CanvasText.measure(str, style),

+        height = style.size || CanvasText.style.size,

+        angle = style.angle || CanvasText.style.angle;

+

+    if (style.angle == 0) return {width: width, height: height};

+    return {

+      width:  Math.abs(Math.cos(angle) * width) + Math.abs(Math.sin(angle) * height),

+      height: Math.abs(Math.sin(angle) * width) + Math.abs(Math.cos(angle) * height)

+    }

+  },

+  

+  getBestAlign: function(angle, style) {

+    angle += CanvasText.getAngleFromAlign(style.halign, style.valign);

+    var a = {h:'c', v:'m'};

+    if (Math.round(Math.cos(angle)*1000)/1000 != 0) 

+      a.h = (Math.cos(angle) > 0 ? 'r' : 'l');

+    

+    if (Math.round(Math.sin(angle)*1000)/1000 != 0) 

+      a.v = (Math.sin(angle) > 0 ? 't' : 'b');

+    return a;

+  },

+  

+  getAngleFromAlign: function(halign, valign) {

+    var pi = Math.PI, table = {

+      'rm': 0,

+      'rt': pi/4,

+      'ct': pi/2,

+      'lt': 3*(pi/4),

+      'lm': pi,

+      'lb': -3*(pi/4),

+      'cb': -pi/2,

+      'rb': -pi/4,

+      'cm': 0

+    }

+    return table[halign+valign];

+  },

+  

+  /** Draws serie of points at given coordinates 

+   * @param {Canvas context} ctx - The canvas context

+   * @param {Array} points - The points to draw

+   * @param {Number} x - The X coordinate

+   * @param {Number} y - The Y coordinate

+   * @param {Number} mag - The scale 

+   */

+  drawPoints: function (ctx, points, x, y, mag, offset) {

+    var i, a, penUp = true, needStroke = 0;

+    offset = offset || {x:0, y:0};

+    

+    ctx.beginPath();

+    for (i = 0; i < points.length; i++) {

+      a = points[i];

+      if (!a) {

+        penUp = true;

+        continue;

+      }

+      if (penUp) {

+        ctx.moveTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y);

+        penUp = false;

+      }

+      else {

+        ctx.lineTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y);

+      }

+    }

+    ctx.stroke();

+  },

+  

+  /** Draws a text at given coordinates and with a given style

+   * @param {Canvas context} ctx - The canvas context

+   * @param {String} str - The text to draw

+   * @param {Number} xOrig - The X coordinate

+   * @param {Number} yOrig - The Y coordinate

+   * @param {Object} style - The font style

+   */

+  draw: function(ctx, str, xOrig, yOrig, style) {

+    if (!str) return;

+    style = style || CanvasText.style;

+    style.halign = style.halign || CanvasText.style.halign;

+    style.valign = style.valign || CanvasText.style.valign;

+    style.angle = style.angle || CanvasText.style.angle;

+    style.size = style.size || CanvasText.style.size;

+    style.adjustAlign = style.adjustAlign || CanvasText.style.adjustAlign;

+    

+    var i, c, total = 0,

+        mag = style.size / 25.0,

+        x = 0, y = 0,

+        lexemes = CanvasText.parseLexemes(str);

+    

+    var offset = {x:0, y:0}, 

+        measure = CanvasText.measure(str, style),

+        align;

+        

+    if (style.adjustAlign) {

+      align = CanvasText.getBestAlign(style.angle, style);

+      style.halign = align.h;

+      style.valign = align.v;

+    }

+        

+    switch (style.halign) {

+      case 'l': break;

+      case 'c': offset.x = -measure / 2; break;

+      case 'r': offset.x = -measure; break;

+    }

+    

+    switch (style.valign) {

+      case 'b': break;

+      case 'm': offset.y = style.size / 2; break;

+      case 't': offset.y = style.size; break;

+    }

+    

+    ctx.save();

+    ctx.translate(xOrig, yOrig);

+    ctx.rotate(style.angle);

+    ctx.lineCap = "round";

+    ctx.lineWidth = 2.0 * mag * (style.weight || CanvasText.style.weight);

+    ctx.strokeStyle = style.color || CanvasText.style.color;

+    

+    for (i = 0; i < lexemes.length; i++) {

+    	c = lexemes[i];

+      if (c.width == -1) {

+        x = 0;

+        y = style.size * 1.4;

+        continue;

+      }

+    

+      var points = c.points,

+          width = c.width;

+          

+      if (c.diacritic) {

+        var dia = CanvasText.diacritics[c.diacritic];

+        var char = CanvasText.letter(c.letter);

+

+        CanvasText.drawPoints(ctx, dia.points, x, y - (c.letter.toUpperCase() == c.letter ? 3 : 0), mag, offset);

+        points = char.points;

+        width = char.width;

+      }

+

+      CanvasText.drawPoints(ctx, points, x, y, mag, offset);

+      

+      if (CanvasText.debug) {

+      	ctx.save();

+        ctx.lineJoin = "miter";

+        ctx.lineWidth = 0.5;

+        ctx.strokeStyle = (style.boundingBoxColor || CanvasText.style.boundingBoxColor);

+      	ctx.strokeRect(x+offset.x, y+offset.y, width*mag, -style.size);

+        

+        ctx.fillStyle = (style.originPointColor || CanvasText.style.originPointColor);

+        ctx.beginPath();

+        ctx.arc(0, 0, 1.5, 0, Math.PI*2, true);

+        ctx.fill();

+        

+      	ctx.restore();

+      }

+      

+      x += width*mag*(style.tracking || CanvasText.style.tracking);

+    }

+    ctx.restore();

+    return total;

+  },

+  

+  /** Enables the text function for a Canvas context

+   * @param {Canvas context} ctx - The canvas context

+   */

+  enable: function(ctx) {

+    ctx.drawText    = function(text, x, y, style) { return CanvasText.draw(ctx, text, x, y, style); };

+    ctx.measureText = function(text, style) { return CanvasText.measure(text, style); };

+    ctx.getTextBounds = function(text, style) { return CanvasText.getDimensions(text, style); };

+    ctx.fontAscent  = function(style) { return CanvasText.ascent(style); };

+    ctx.fontDescent = function(style) { return CanvasText.descent(style); };

+  }

+};

--- /dev/null
+++ b/js/flotr/lib/excanvas.js
@@ -1,1 +1,885 @@
-
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns are not implemented.
+// * Radial gradient are not implemented. The VML version of these look very
+//   different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+//   width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+//   Quirks mode will draw the canvas using border-box. Either change your
+//   doctype to HTML5
+//   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+//   or use Box Sizing Behavior from WebFX
+//   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+  var abs = m.abs;
+  var sqrt = m.sqrt;
+
+  // this is used for sub pixel precision
+  var Z = 10;
+  var Z2 = Z / 2;
+
+  /**
+   * This funtion is assigned to the <canvas> elements as element.getContext().
+   * @this {HTMLElement}
+   * @return {CanvasRenderingContext2D_}
+   */
+  function getContext() {
+    return this.context_ ||
+        (this.context_ = new CanvasRenderingContext2D_(this));
+  }
+
+  var slice = Array.prototype.slice;
+
+  /**
+   * Binds a function to an object. The returned function will always use the
+   * passed in {@code obj} as {@code this}.
+   *
+   * Example:
+   *
+   *   g = bind(f, obj, a, b)
+   *   g(c, d) // will do f.call(obj, a, b, c, d)
+   *
+   * @param {Function} f The function to bind the object to
+   * @param {Object} obj The object that should act as this when the function
+   *     is called
+   * @param {*} var_args Rest arguments that will be used as the initial
+   *     arguments when the function is called
+   * @return {Function} A new function that has bound this
+   */
+  function bind(f, obj, var_args) {
+    var a = slice.call(arguments, 2);
+    return function() {
+      return f.apply(obj, a.concat(slice.call(arguments)));
+    };
+  }
+
+  var G_vmlCanvasManager_ = {
+    init: function(opt_doc) {
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var doc = opt_doc || document;
+        // Create a dummy element so that IE will allow canvas elements to be
+        // recognized.
+        doc.createElement('canvas');
+        doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+      }
+    },
+
+    init_: function(doc) {
+      // create xmlns
+      if (!doc.namespaces['g_vml_']) {
+        doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
+                           '#default#VML');
+
+      }
+      if (!doc.namespaces['g_o_']) {
+        doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
+                           '#default#VML');
+      }
+
+      // Setup default CSS.  Only add one style sheet per document
+      if (!doc.styleSheets['ex_canvas_']) {
+        var ss = doc.createStyleSheet();
+        ss.owningElement.id = 'ex_canvas_';
+        ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+            // default size is 300x150 in Gecko and Opera
+            'text-align:left;width:300px;height:150px}' +
+            'g_vml_\\:*{behavior:url(#default#VML)}' +
+            'g_o_\\:*{behavior:url(#default#VML)}';
+
+      }
+
+      // find all canvas elements
+      var els = doc.getElementsByTagName('canvas');
+      for (var i = 0; i < els.length; i++) {
+        this.initElement(els[i]);
+      }
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function(el) {
+      if (!el.getContext) {
+
+        el.getContext = getContext;
+
+        // Remove fallback content. There is no way to hide text nodes so we
+        // just remove all childNodes. We could hide all elements and remove
+        // text nodes but who really cares about the fallback content.
+        el.innerHTML = '';
+
+        // do not use inline function because that will leak memory
+        el.attachEvent('onpropertychange', onPropertyChange);
+        el.attachEvent('onresize', onResize);
+
+        var attrs = el.attributes;
+        if (attrs.width && attrs.width.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setWidth_(attrs.width.nodeValue);
+          el.style.width = attrs.width.nodeValue + 'px';
+        } else {
+          el.width = el.clientWidth;
+        }
+        if (attrs.height && attrs.height.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setHeight_(attrs.height.nodeValue);
+          el.style.height = attrs.height.nodeValue + 'px';
+        } else {
+          el.height = el.clientHeight;
+        }
+        //el.getContext().setCoordsize_()
+      }
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    var el = e.srcElement;
+
+    switch (e.propertyName) {
+      case 'width':
+        el.style.width = el.attributes.width.nodeValue + 'px';
+        el.getContext().clearRect();
+        break;
+      case 'height':
+        el.style.height = el.attributes.height.nodeValue + 'px';
+        el.getContext().clearRect();
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var dec2hex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+    o2.globalAlpha   = o1.globalAlpha;
+    o2.arcScaleX_    = o1.arcScaleX_;
+    o2.arcScaleY_    = o1.arcScaleY_;
+    o2.lineScale_    = o1.lineScale_;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.substring(0, 3) == 'rgb') {
+      var start = styleString.indexOf('(', 3);
+      var end = styleString.indexOf(')', start + 1);
+      var guts = styleString.substring(start + 1, end).split(',');
+
+      str = '#';
+      for (var i = 0; i < 3; i++) {
+        str += dec2hex[Number(guts[i])];
+      }
+
+      if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
+        alpha = guts[3];
+      }
+    } else {
+      str = styleString;
+    }
+
+    return {color: str, alpha: alpha};
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case 'butt':
+        return 'flat';
+      case 'round':
+        return 'round';
+      case 'square':
+      default:
+        return 'square';
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+  function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = '#000';
+    this.fillStyle = '#000';
+
+    this.lineWidth = 1;
+    this.lineJoin = 'miter';
+    this.lineCap = 'butt';
+    this.miterLimit = Z * 1;
+    this.globalAlpha = 1;
+    this.canvas = surfaceElement;
+
+    var el = surfaceElement.ownerDocument.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+    this.lineScale_ = 1;
+  }
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    this.element_.innerHTML = '';
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    var cp1 = this.getCoords_(aCP1x, aCP1y);
+    var cp2 = this.getCoords_(aCP2x, aCP2y);
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  // Helper function that takes the already fixed cordinates.
+  function bezierCurveTo(self, cp1, cp2, p) {
+    self.currentPath_.push({
+      type: 'bezierCurveTo',
+      cp1x: cp1.x,
+      cp1y: cp1.y,
+      cp2x: cp2.x,
+      cp2y: cp2.y,
+      x: p.x,
+      y: p.y
+    });
+    self.currentX_ = p.x;
+    self.currentY_ = p.y;
+  }
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // the following is lifted almost directly from
+    // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+    var cp = this.getCoords_(aCPx, aCPy);
+    var p = this.getCoords_(aX, aY);
+
+    var cp1 = {
+      x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+      y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+    };
+    var cp2 = {
+      x: cp1.x + (p.x - this.currentX_) / 3.0,
+      y: cp1.y + (p.y - this.currentY_) / 3.0
+    };
+
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= Z;
+    var arcType = aClockwise ? 'at' : 'wa';
+
+    var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+    var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+    var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+    var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+    // IE won't render arches drawn counter clockwise if xStart == xEnd.
+    if (xStart == xEnd && !aClockwise) {
+      xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+                       // that can be represented in binary
+    }
+
+    var p = this.getCoords_(aX, aY);
+    var pStart = this.getCoords_(xStart, yStart);
+    var pEnd = this.getCoords_(xEnd, yEnd);
+
+    this.currentPath_.push({type: arcType,
+                           x: p.x,
+                           y: p.y,
+                           radius: aRadius,
+                           xStart: pStart.x,
+                           yStart: pStart.y,
+                           xEnd: pEnd.x,
+                           yEnd: pEnd.y});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_('gradient');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+                                                   aX1, aY1, aR1) {
+    var gradient = new CanvasGradient_('gradientradial');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.r0_ = aR0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    gradient.r1_ = aR1;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function(image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+
+    // to find the original width we overide the width and height
+    var oldRuntimeWidth = image.runtimeStyle.width;
+    var oldRuntimeHeight = image.runtimeStyle.height;
+    image.runtimeStyle.width = 'auto';
+    image.runtimeStyle.height = 'auto';
+
+    // get the original size
+    var w = image.width;
+    var h = image.height;
+
+    // and remove overides
+    image.runtimeStyle.width = oldRuntimeWidth;
+    image.runtimeStyle.height = oldRuntimeHeight;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw Error('Invalid number of arguments');
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = sw / 2;
+    var h2 = sh / 2;
+
+    var vmlStr = [];
+
+    var W = 10;
+    var H = 10;
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="', Z * W, ',', Z * H, '"',
+                ' coordorigin="0,0"' ,
+                ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push('M11=', this.m_[0][0], ',',
+                  'M12=', this.m_[1][0], ',',
+                  'M21=', this.m_[0][1], ',',
+                  'M22=', this.m_[1][1], ',',
+                  'Dx=', mr(d.x / Z), ',',
+                  'Dy=', mr(d.y / Z), '');
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx + dw, dy);
+      var c3 = this.getCoords_(dx, dy + dh);
+      var c4 = this.getCoords_(dx + dw, dy + dh);
+
+      max.x = m.max(max.x, c2.x, c3.x, c4.x);
+      max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+                  'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+                  filter.join(''), ", sizingmethod='clip');")
+    } else {
+      vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', Z * dw, 'px;',
+                ' height:', Z * dh, 'px;"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML('BeforeEnd',
+                                    vmlStr.join(''));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var lineStr = [];
+    var lineOpen = false;
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+    var color = a.color;
+    var opacity = a.alpha * this.globalAlpha;
+
+    var W = 10;
+    var H = 10;
+
+    lineStr.push('<g_vml_:shape',
+                 ' filled="', !!aFill, '"',
+                 ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+                 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
+                 ' stroked="', !aFill, '"',
+                 ' path="');
+
+    var newSeq = false;
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var i = 0; i < this.currentPath_.length; i++) {
+      var p = this.currentPath_[i];
+      var c;
+
+      switch (p.type) {
+        case 'moveTo':
+          c = p;
+          lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+          break;
+        case 'lineTo':
+          lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+          break;
+        case 'close':
+          lineStr.push(' x ');
+          p = null;
+          break;
+        case 'bezierCurveTo':
+          lineStr.push(' c ',
+                       mr(p.cp1x), ',', mr(p.cp1y), ',',
+                       mr(p.cp2x), ',', mr(p.cp2y), ',',
+                       mr(p.x), ',', mr(p.y));
+          break;
+        case 'at':
+        case 'wa':
+          lineStr.push(' ', p.type, ' ',
+                       mr(p.x - this.arcScaleX_ * p.radius), ',',
+                       mr(p.y - this.arcScaleY_ * p.radius), ' ',
+                       mr(p.x + this.arcScaleX_ * p.radius), ',',
+                       mr(p.y + this.arcScaleY_ * p.radius), ' ',
+                       mr(p.xStart), ',', mr(p.yStart), ' ',
+                       mr(p.xEnd), ',', mr(p.yEnd));
+          break;
+      }
+
+
+      // TODO: Following is broken for curves due to
+      //       move to proper paths.
+
+      // Figure out dimensions so we can do gradient fills
+      // properly
+      if (p) {
+        if (min.x == null || p.x < min.x) {
+          min.x = p.x;
+        }
+        if (max.x == null || p.x > max.x) {
+          max.x = p.x;
+        }
+        if (min.y == null || p.y < min.y) {
+          min.y = p.y;
+        }
+        if (max.y == null || p.y > max.y) {
+          max.y = p.y;
+        }
+      }
+    }
+    lineStr.push(' ">');
+
+    if (!aFill) {
+      var lineWidth = this.lineScale_ * this.lineWidth;
+
+      // VML cannot correctly render a line if the width is less than 1px.
+      // In that case, we dilute the color to make the line look thinner.
+      if (lineWidth < 1) {
+        opacity *= lineWidth;
+      }
+
+      lineStr.push(
+        '<g_vml_:stroke',
+        ' opacity="', opacity, '"',
+        ' joinstyle="', this.lineJoin, '"',
+        ' miterlimit="', this.miterLimit, '"',
+        ' endcap="', processLineCap(this.lineCap), '"',
+        ' weight="', lineWidth, 'px"',
+        ' color="', color, '" />'
+      );
+    } else if (typeof this.fillStyle == 'object') {
+      var fillStyle = this.fillStyle;
+      var angle = 0;
+      var focus = {x: 0, y: 0};
+
+      // additional offset
+      var shift = 0;
+      // scale factor for offset
+      var expansion = 1;
+
+      if (fillStyle.type_ == 'gradient') {
+        var x0 = fillStyle.x0_ / this.arcScaleX_;
+        var y0 = fillStyle.y0_ / this.arcScaleY_;
+        var x1 = fillStyle.x1_ / this.arcScaleX_;
+        var y1 = fillStyle.y1_ / this.arcScaleY_;
+        var p0 = this.getCoords_(x0, y0);
+        var p1 = this.getCoords_(x1, y1);
+        var dx = p1.x - p0.x;
+        var dy = p1.y - p0.y;
+        angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+        // The angle should be a non-negative number.
+        if (angle < 0) {
+          angle += 360;
+        }
+
+        // Very small angles produce an unexpected result because they are
+        // converted to a scientific notation string.
+        if (angle < 1e-6) {
+          angle = 0;
+        }
+      } else {
+        var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
+        var width  = max.x - min.x;
+        var height = max.y - min.y;
+        focus = {
+          x: (p0.x - min.x) / width,
+          y: (p0.y - min.y) / height
+        };
+
+        width  /= this.arcScaleX_ * Z;
+        height /= this.arcScaleY_ * Z;
+        var dimension = m.max(width, height);
+        shift = 2 * fillStyle.r0_ / dimension;
+        expansion = 2 * fillStyle.r1_ / dimension - shift;
+      }
+
+      // We need to sort the color stops in ascending order by offset,
+      // otherwise IE won't interpret it correctly.
+      var stops = fillStyle.colors_;
+      stops.sort(function(cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      var length = stops.length;
+      var color1 = stops[0].color;
+      var color2 = stops[length - 1].color;
+      var opacity1 = stops[0].alpha * this.globalAlpha;
+      var opacity2 = stops[length - 1].alpha * this.globalAlpha;
+
+      var colors = [];
+      for (var i = 0; i < length; i++) {
+        var stop = stops[i];
+        colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+      }
+
+      // When colors attribute is used, the meanings of opacity and o:opacity2
+      // are reversed.
+      lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+                   ' method="none" focus="100%"',
+                   ' color="', color1, '"',
+                   ' color2="', color2, '"',
+                   ' colors="', colors.join(','), '"',
+                   ' opacity="', opacity2, '"',
+                   ' g_o_:opacity2="', opacity1, '"',
+                   ' angle="', angle, '"',
+                   ' focusposition="', focus.x, ',', focus.y, '" />');
+    } else {
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+                   '" />');
+    }
+
+    lineStr.push('</g_vml_:shape>');
+
+    this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+  };
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  }
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: 'close'});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    var m = this.m_;
+    return {
+      x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+      y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+    }
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    copyState(this.aStack_.pop(), this);
+    this.m_ = this.mStack_.pop();
+  };
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    var m = this.m_ = matrixMultiply(m1, this.m_);
+
+    // Get the line scale.
+    // Determinant of this.m_ means how much the area is enlarged by the
+    // transformation. So its square root can be used as a scale factor
+    // for width.
+    var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+    this.lineScale_ = sqrt(abs(det));
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function() {
+    return new CanvasPattern_;
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.x0_ = 0;
+    this.y0_ = 0;
+    this.r0_ = 0;
+    this.x1_ = 0;
+    this.y1_ = 0;
+    this.r1_ = 0;
+    this.colors_ = [];
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: aOffset,
+                       color: aColor.color,
+                       alpha: aColor.alpha});
+  };
+
+  function CanvasPattern_() {}
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if
+

--- /dev/null
+++ b/js/flotr/lib/prototype-1.6.0.2.js
@@ -1,1 +1,4221 @@
-
+/*  Prototype JavaScript framework, version 1.6.0.2

+ *  (c) 2005-2008 Sam Stephenson

+ *

+ *  Prototype is freely distributable under the terms of an MIT-style license.

+ *  For details, see the Prototype web site: http://www.prototypejs.org/

+ *

+ *--------------------------------------------------------------------------*/

+

+var Prototype = {

+  Version: '1.6.0.2',

+

+  Browser: {

+    IE:     !!(window.attachEvent && !window.opera),

+    Opera:  !!window.opera,

+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,

+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,

+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)

+  },

+

+  BrowserFeatures: {

+    XPath: !!document.evaluate,

+    ElementExtensions: !!window.HTMLElement,

+    SpecificElementExtensions:

+      document.createElement('div').__proto__ &&

+      document.createElement('div').__proto__ !==

+        document.createElement('form').__proto__

+  },

+

+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',

+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

+

+  emptyFunction: function() { },

+  K: function(x) { return x }

+};

+

+if (Prototype.Browser.MobileSafari)

+  Prototype.BrowserFeatures.SpecificElementExtensions = false;

+

+

+/* Based on Alex Arnell's inheritance implementation. */

+var Class = {

+  create: function() {

+    var parent = null, properties = $A(arguments);

+    if (Object.isFunction(properties[0]))

+      parent = properties.shift();

+

+    function klass() {

+      this.initialize.apply(this, arguments);

+    }

+

+    Object.extend(klass, Class.Methods);

+    klass.superclass = parent;

+    klass.subclasses = [];

+

+    if (parent) {

+      var subclass = function() { };

+      subclass.prototype = parent.prototype;

+      klass.prototype = new subclass;

+      parent.subclasses.push(klass);

+    }

+

+    for (var i = 0; i < properties.length; i++)

+      klass.addMethods(properties[i]);

+

+    if (!klass.prototype.initialize)

+      klass.prototype.initialize = Prototype.emptyFunction;

+

+    klass.prototype.constructor = klass;

+

+    return klass;

+  }

+};

+

+Class.Methods = {

+  addMethods: function(source) {

+    var ancestor   = this.superclass && this.superclass.prototype;

+    var properties = Object.keys(source);

+

+    if (!Object.keys({ toString: true }).length)

+      properties.push("toString", "valueOf");

+

+    for (var i = 0, length = properties.length; i < length; i++) {

+      var property = properties[i], value = source[property];

+      if (ancestor && Object.isFunction(value) &&

+          value.argumentNames().first() == "$super") {

+        var method = value, value = Object.extend((function(m) {

+          return function() { return ancestor[m].apply(this, arguments) };

+        })(property).wrap(method), {

+          valueOf:  function() { return method },

+          toString: function() { return method.toString() }

+        });

+      }

+      this.prototype[property] = value;

+    }

+

+    return this;

+  }

+};

+

+var Abstract = { };

+

+Object.extend = function(destination, source) {

+  for (var property in source)

+    destination[property] = source[property];

+  return destination;

+};

+

+Object.extend(Object, {

+  inspect: function(object) {

+    try {

+      if (Object.isUndefined(object)) return 'undefined';

+      if (object === null) return 'null';

+      return object.inspect ? object.inspect() : String(object);

+    } catch (e) {

+      if (e instanceof RangeError) return '...';

+      throw e;

+    }

+  },

+

+  toJSON: function(object) {

+    var type = typeof object;

+    switch (type) {

+      case 'undefined':

+      case 'function':

+      case 'unknown': return;

+      case 'boolean': return object.toString();

+    }

+

+    if (object === null) return 'null';

+    if (object.toJSON) return object.toJSON();

+    if (Object.isElement(object)) return;

+

+    var results = [];

+    for (var property in object) {

+      var value = Object.toJSON(object[property]);

+      if (!Object.isUndefined(value))

+        results.push(property.toJSON() + ': ' + value);

+    }

+

+    return '{' + results.join(', ') + '}';

+  },

+

+  toQueryString: function(object) {

+    return $H(object).toQueryString();

+  },

+

+  toHTML: function(object) {

+    return object && object.toHTML ? object.toHTML() : String.interpret(object);

+  },

+

+  keys: function(object) {

+    var keys = [];

+    for (var property in object)

+      keys.push(property);

+    return keys;

+  },

+

+  values: function(object) {

+    var values = [];

+    for (var property in object)

+      values.push(object[property]);

+    return values;

+  },

+

+  clone: function(object) {

+    return Object.extend({ }, object);

+  },

+

+  isElement: function(object) {

+    return object && object.nodeType == 1;

+  },

+

+  isArray: function(object) {

+    return object != null && typeof object == "object" &&

+      'splice' in object && 'join' in object;

+  },

+

+  isHash: function(object) {

+    return object instanceof Hash;

+  },

+

+  isFunction: function(object) {

+    return typeof object == "function";

+  },

+

+  isString: function(object) {

+    return typeof object == "string";

+  },

+

+  isNumber: function(object) {

+    return typeof object == "number";

+  },

+

+  isUndefined: function(object) {

+    return typeof object == "undefined";

+  }

+});

+

+Object.extend(Function.prototype, {

+  argumentNames: function() {

+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");

+    return names.length == 1 && !names[0] ? [] : names;

+  },

+

+  bind: function() {

+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;

+    var __method = this, args = $A(arguments), object = args.shift();

+    return function() {

+      return __method.apply(object, args.concat($A(arguments)));

+    }

+  },

+

+  bindAsEventListener: function() {

+    var __method = this, args = $A(arguments), object = args.shift();

+    return function(event) {

+      return __method.apply(object, [event || window.event].concat(args));

+    }

+  },

+

+  curry: function() {

+    if (!arguments.length) return this;

+    var __method = this, args = $A(arguments);

+    return function() {

+      return __method.apply(this, args.concat($A(arguments)));

+    }

+  },

+

+  delay: function() {

+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;

+    return window.setTimeout(function() {

+      return __method.apply(__method, args);

+    }, timeout);

+  },

+

+  wrap: function(wrapper) {

+    var __method = this;

+    return function() {

+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));

+    }

+  },

+

+  methodize: function() {

+    if (this._methodized) return this._methodized;

+    var __method = this;

+    return this._methodized = function() {

+      return __method.apply(null, [this].concat($A(arguments)));

+    };

+  }

+});

+

+Function.prototype.defer = Function.prototype.delay.curry(0.01);

+

+Date.prototype.toJSON = function() {

+  return '"' + this.getUTCFullYear() + '-' +

+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +

+    this.getUTCDate().toPaddedString(2) + 'T' +

+    this.getUTCHours().toPaddedString(2) + ':' +

+    this.getUTCMinutes().toPaddedString(2) + ':' +

+    this.getUTCSeconds().toPaddedString(2) + 'Z"';

+};

+

+var Try = {

+  these: function() {

+    var returnValue;

+

+    for (var i = 0, length = arguments.length; i < length; i++) {

+      var lambda = arguments[i];

+      try {

+        returnValue = lambda();

+        break;

+      } catch (e) { }

+    }

+

+    return returnValue;

+  }

+};

+

+RegExp.prototype.match = RegExp.prototype.test;

+

+RegExp.escape = function(str) {

+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');

+};

+

+/*--------------------------------------------------------------------------*/

+

+var PeriodicalExecuter = Class.create({

+  initialize: function(callback, frequency) {

+    this.callback = callback;

+    this.frequency = frequency;

+    this.currentlyExecuting = false;

+

+    this.registerCallback();

+  },

+

+  registerCallback: function() {

+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);

+  },

+

+  execute: function() {

+    this.callback(this);

+  },

+

+  stop: function() {

+    if (!this.timer) return;

+    clearInterval(this.timer);

+    this.timer = null;

+  },

+

+  onTimerEvent: function() {

+    if (!this.currentlyExecuting) {

+      try {

+        this.currentlyExecuting = true;

+        this.execute();

+      } finally {

+        this.currentlyExecuting = false;

+      }

+    }

+  }

+});

+Object.extend(String, {

+  interpret: function(value) {

+    return value == null ? '' : String(value);

+  },

+  specialChar: {

+    '\b': '\\b',

+    '\t': '\\t',

+    '\n': '\\n',

+    '\f': '\\f',

+    '\r': '\\r',

+    '\\': '\\\\'

+  }

+});

+

+Object.extend(String.prototype, {

+  gsub: function(pattern, replacement) {

+    var result = '', source = this, match;

+    replacement = arguments.callee.prepareReplacement(replacement);

+

+    while (source.length > 0) {

+      if (match = source.match(pattern)) {

+        result += source.slice(0, match.index);

+        result += String.interpret(replacement(match));

+        source  = source.slice(match.index + match[0].length);

+      } else {

+        result += source, source = '';

+      }

+    }

+    return result;

+  },

+

+  sub: function(pattern, replacement, count) {

+    replacement = this.gsub.prepareReplacement(replacement);

+    count = Object.isUndefined(count) ? 1 : count;

+

+    return this.gsub(pattern, function(match) {

+      if (--count < 0) return match[0];

+      return replacement(match);

+    });

+  },

+

+  scan: function(pattern, iterator) {

+    this.gsub(pattern, iterator);

+    return String(this);

+  },

+

+  truncate: function(length, truncation) {

+    length = length || 30;

+    truncation = Object.isUndefined(truncation) ? '...' : truncation;

+    return this.length > length ?

+      this.slice(0, length - truncation.length) + truncation : String(this);

+  },

+

+  strip: function() {

+    return this.replace(/^\s+/, '').replace(/\s+$/, '');

+  },

+

+  stripTags: function() {

+    return this.replace(/<\/?[^>]+>/gi, '');

+  },

+

+  stripScripts: function() {

+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');

+  },

+

+  extractScripts: function() {

+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');

+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');

+    return (this.match(matchAll) || []).map(function(scriptTag) {

+      return (scriptTag.match(matchOne) || ['', ''])[1];

+    });

+  },

+

+  evalScripts: function() {

+    return this.extractScripts().map(function(script) { return eval(script) });

+  },

+

+  escapeHTML: function() {

+    var self = arguments.callee;

+    self.text.data = this;

+    return self.div.innerHTML;

+  },

+

+  unescapeHTML: function() {

+    var div = new Element('div');

+    div.innerHTML = this.stripTags();

+    return div.childNodes[0] ? (div.childNodes.length > 1 ?

+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :

+      div.childNodes[0].nodeValue) : '';

+  },

+

+  toQueryParams: function(separator) {

+    var match = this.strip().match(/([^?#]*)(#.*)?$/);

+    if (!match) return { };

+

+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {

+      if ((pair = pair.split('='))[0]) {

+        var key = decodeURIComponent(pair.shift());

+        var value = pair.length > 1 ? pair.join('=') : pair[0];

+        if (value != undefined) value = decodeURIComponent(value);

+

+        if (key in hash) {

+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];

+          hash[key].push(value);

+        }

+        else hash[key] = value;

+      }

+      return hash;

+    });

+  },

+

+  toArray: function() {

+    return this.split('');

+  },

+

+  succ: function() {

+    return this.slice(0, this.length - 1) +

+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);

+  },

+

+  times: function(count) {

+    return count < 1 ? '' : new Array(count + 1).join(this);

+  },

+

+  camelize: function() {

+    var parts = this.split('-'), len = parts.length;

+    if (len == 1) return parts[0];

+

+    var camelized = this.charAt(0) == '-'

+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)

+      : parts[0];

+

+    for (var i = 1; i < len; i++)

+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

+

+    return camelized;

+  },

+

+  capitalize: function() {

+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();

+  },

+

+  underscore: function() {

+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();

+  },

+

+  dasherize: function() {

+    return this.gsub(/_/,'-');

+  },

+

+  inspect: function(useDoubleQuotes) {

+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {

+      var character = String.specialChar[match[0]];

+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);

+    });

+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';

+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";

+  },

+

+  toJSON: function() {

+    return this.inspect(true);

+  },

+

+  unfilterJSON: function(filter) {

+    return this.sub(filter || Prototype.JSONFilter, '#{1}');

+  },

+

+  isJSON: function() {

+    var str = this;

+    if (str.blank()) return false;

+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');

+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);

+  },

+

+  evalJSON: function(sanitize) {

+    var json = this.unfilterJSON();

+    try {

+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');

+    } catch (e) { }

+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());

+  },

+

+  include: function(pattern) {

+    return this.indexOf(pattern) > -1;

+  },

+

+  startsWith: function(pattern) {

+    return this.indexOf(pattern) === 0;

+  },

+

+  endsWith: function(pattern) {

+    var d = this.length - pattern.length;

+    return d >= 0 && this.lastIndexOf(pattern) === d;

+  },

+

+  empty: function() {

+    return this == '';

+  },

+

+  blank: function() {

+    return /^\s*$/.test(this);

+  },

+

+  interpolate: function(object, pattern) {

+    return new Template(this, pattern).evaluate(object);

+  }

+});

+

+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {

+  escapeHTML: function() {

+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');

+  },

+  unescapeHTML: function() {

+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');

+  }

+});

+

+String.prototype.gsub.prepareReplacement = function(replacement) {

+  if (Object.isFunction(replacement)) return replacement;

+  var template = new Template(replacement);

+  return function(match) { return template.evaluate(match) };

+};

+

+String.prototype.parseQuery = String.prototype.toQueryParams;

+

+Object.extend(String.prototype.escapeHTML, {

+  div:  document.createElement('div'),

+  text: document.createTextNode('')

+});

+

+with (String.prototype.escapeHTML) div.appendChild(text);

+

+var Template = Class.create({

+  initialize: function(template, pattern) {

+    this.template = template.toString();

+    this.pattern = pattern || Template.Pattern;

+  },

+

+  evaluate: function(object) {

+    if (Object.isFunction(object.toTemplateReplacements))

+      object = object.toTemplateReplacements();

+

+    return this.template.gsub(this.pattern, function(match) {

+      if (object == null) return '';

+

+      var before = match[1] || '';

+      if (before == '\\') return match[2];

+

+      var ctx = object, expr = match[3];

+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;

+      match = pattern.exec(expr);

+      if (match == null) return before;

+

+      while (match != null) {

+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];

+        ctx = ctx[comp];

+        if (null == ctx || '' == match[3]) break;

+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);

+        match = pattern.exec(expr);

+      }

+

+      return before + String.interpret(ctx);

+    });

+  }

+});

+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

+

+var $break = { };

+

+var Enumerable = {

+  each: function(iterator, context) {

+    var index = 0;

+    iterator = iterator.bind(context);

+    try {

+      this._each(function(value) {

+        iterator(value, index++);

+      });

+    } catch (e) {

+      if (e != $break) throw e;

+    }

+    return this;

+  },

+

+  eachSlice: function(number, iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var index = -number, slices = [], array = this.toArray();

+    while ((index += number) < array.length)

+      slices.push(array.slice(index, index+number));

+    return slices.collect(iterator, context);

+  },

+

+  all: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var result = true;

+    this.each(function(value, index) {

+      result = result && !!iterator(value, index);

+      if (!result) throw $break;

+    });

+    return result;

+  },

+

+  any: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var result = false;

+    this.each(function(value, index) {

+      if (result = !!iterator(value, index))

+        throw $break;

+    });

+    return result;

+  },

+

+  collect: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var results = [];

+    this.each(function(value, index) {

+      results.push(iterator(value, index));

+    });

+    return results;

+  },

+

+  detect: function(iterator, context) {

+    iterator = iterator.bind(context);

+    var result;

+    this.each(function(value, index) {

+      if (iterator(value, index)) {

+        result = value;

+        throw $break;

+      }

+    });

+    return result;

+  },

+

+  findAll: function(iterator, context) {

+    iterator = iterator.bind(context);

+    var results = [];

+    this.each(function(value, index) {

+      if (iterator(value, index))

+        results.push(value);

+    });

+    return results;

+  },

+

+  grep: function(filter, iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var results = [];

+

+    if (Object.isString(filter))

+      filter = new RegExp(filter);

+

+    this.each(function(value, index) {

+      if (filter.match(value))

+        results.push(iterator(value, index));

+    });

+    return results;

+  },

+

+  include: function(object) {

+    if (Object.isFunction(this.indexOf))

+      if (this.indexOf(object) != -1) return true;

+

+    var found = false;

+    this.each(function(value) {

+      if (value == object) {

+        found = true;

+        throw $break;

+      }

+    });

+    return found;

+  },

+

+  inGroupsOf: function(number, fillWith) {

+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;

+    return this.eachSlice(number, function(slice) {

+      while(slice.length < number) slice.push(fillWith);

+      return slice;

+    });

+  },

+

+  inject: function(memo, iterator, context) {

+    iterator = iterator.bind(context);

+    this.each(function(value, index) {

+      memo = iterator(memo, value, index);

+    });

+    return memo;

+  },

+

+  invoke: function(method) {

+    var args = $A(arguments).slice(1);

+    return this.map(function(value) {

+      return value[method].apply(value, args);

+    });

+  },

+

+  max: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var result;

+    this.each(function(value, index) {

+      value = iterator(value, index);

+      if (result == null || value >= result)

+        result = value;

+    });

+    return result;

+  },

+

+  min: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var result;

+    this.each(function(value, index) {

+      value = iterator(value, index);

+      if (result == null || value < result)

+        result = value;

+    });

+    return result;

+  },

+

+  partition: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var trues = [], falses = [];

+    this.each(function(value, index) {

+      (iterator(value, index) ?

+        trues : falses).push(value);

+    });

+    return [trues, falses];

+  },

+

+  pluck: function(property) {

+    var results = [];

+    this.each(function(value) {

+      results.push(value[property]);

+    });

+    return results;

+  },

+

+  reject: function(iterator, context) {

+    iterator = iterator.bind(context);

+    var results = [];

+    this.each(function(value, index) {

+      if (!iterator(value, index))

+        results.push(value);

+    });

+    return results;

+  },

+

+  sortBy: function(iterator, context) {

+    iterator = iterator.bind(context);

+    return this.map(function(value, index) {

+      return {value: value, criteria: iterator(value, index)};

+    }).sort(function(left, right) {

+      var a = left.criteria, b = right.criteria;

+      return a < b ? -1 : a > b ? 1 : 0;

+    }).pluck('value');

+  },

+

+  toArray: function() {

+    return this.map();

+  },

+

+  zip: function() {

+    var iterator = Prototype.K, args = $A(arguments);

+    if (Object.isFunction(args.last()))

+      iterator = args.pop();

+

+    var collections = [this].concat(args).map($A);

+    return this.map(function(value, index) {

+      return iterator(collections.pluck(index));

+    });

+  },

+

+  size: function() {

+    return this.toArray().length;

+  },

+

+  inspect: function() {

+    return '#<Enumerable:' + this.toArray().inspect() + '>';

+  }

+};

+

+Object.extend(Enumerable, {

+  map:     Enumerable.collect,

+  find:    Enumerable.detect,

+  select:  Enumerable.findAll,

+  filter:  Enumerable.findAll,

+  member:  Enumerable.include,

+  entries: Enumerable.toArray,

+  every:   Enumerable.all,

+  some:    Enumerable.any

+});

+function $A(iterable) {

+  if (!iterable) return [];

+  if (iterable.toArray) return iterable.toArray();

+  var length = iterable.length || 0, results = new Array(length);

+  while (length--) results[length] = iterable[length];

+  return results;

+}

+

+if (Prototype.Browser.WebKit) {

+  $A = function(iterable) {

+    if (!iterable) return [];

+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&

+        iterable.toArray) return iterable.toArray();

+    var length = iterable.length || 0, results = new Array(length);

+    while (length--) results[length] = iterable[length];

+    return results;

+  };

+}

+

+Array.from = $A;

+

+Object.extend(Array.prototype, Enumerable);

+

+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

+

+Object.extend(Array.prototype, {

+  _each: function(iterator) {

+    for (var i = 0, length = this.length; i < length; i++)

+      iterator(this[i]);

+  },

+

+  clear: function() {

+    this.length = 0;

+    return this;

+  },

+

+  first: function() {

+    return this[0];

+  },

+

+  last: function() {

+    return this[this.length - 1];

+  },

+

+  compact: function() {

+    return this.select(function(value) {

+      return value != null;

+    });

+  },

+

+  flatten: function() {

+    return this.inject([], function(array, value) {

+      return array.concat(Object.isArray(value) ?

+        value.flatten() : [value]);

+    });

+  },

+

+  without: function() {

+    var values = $A(arguments);

+    return this.select(function(value) {

+      return !values.include(value);

+    });

+  },

+

+  reverse: function(inline) {

+    return (inline !== false ? this : this.toArray())._reverse();

+  },

+

+  reduce: function() {

+    return this.length > 1 ? this : this[0];

+  },

+

+  uniq: function(sorted) {

+    return this.inject([], function(array, value, index) {

+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))

+        array.push(value);

+      return array;

+    });

+  },

+

+  intersect: function(array) {

+    return this.uniq().findAll(function(item) {

+      return array.detect(function(value) { return item === value });

+    });

+  },

+

+  clone: function() {

+    return [].concat(this);

+  },

+

+  size: function() {

+    return this.length;

+  },

+

+  inspect: function() {

+    return '[' + this.map(Object.inspect).join(', ') + ']';

+  },

+

+  toJSON: function() {

+    var results = [];

+    this.each(function(object) {

+      var value = Object.toJSON(object);

+      if (!Object.isUndefined(value)) results.push(value);

+    });

+    return '[' + results.join(', ') + ']';

+  }

+});

+

+// use native browser JS 1.6 implementation if available

+if (Object.isFunction(Array.prototype.forEach))

+  Array.prototype._each = Array.prototype.forEach;

+

+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {

+  i || (i = 0);

+  var length = this.length;

+  if (i < 0) i = length + i;

+  for (; i < length; i++)

+    if (this[i] === item) return i;

+  return -1;

+};

+

+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {

+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;

+  var n = this.slice(0, i).reverse().indexOf(item);

+  return (n < 0) ? n : i - n - 1;

+};

+

+Array.prototype.toArray = Array.prototype.clone;

+

+function $w(string) {

+  if (!Object.isString(string)) return [];

+  string = string.strip();

+  return string ? string.split(/\s+/) : [];

+}

+

+if (Prototype.Browser.Opera){

+  Array.prototype.concat = function() {

+    var array = [];

+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);

+    for (var i = 0, length = arguments.length; i < length; i++) {

+      if (Object.isArray(arguments[i])) {

+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)

+          array.push(arguments[i][j]);

+      } else {

+        array.push(arguments[i]);

+      }

+    }

+    return array;

+  };

+}

+Object.extend(Number.prototype, {

+  toColorPart: function() {

+    return this.toPaddedString(2, 16);

+  },

+

+  succ: function() {

+    return this + 1;

+  },

+

+  times: function(iterator) {

+    $R(0, this, true).each(iterator);

+    return this;

+  },

+

+  toPaddedString: function(length, radix) {

+    var string = this.toString(radix || 10);

+    return '0'.times(length - string.length) + string;

+  },

+

+  toJSON: function() {

+    return isFinite(this) ? this.toString() : 'null';

+  }

+});

+

+$w('abs round ceil floor').each(function(method){

+  Number.prototype[method] = Math[method].methodize();

+});

+function $H(object) {

+  return new Hash(object);

+};

+

+var Hash = Class.create(Enumerable, (function() {

+

+  function toQueryPair(key, value) {

+    if (Object.isUndefined(value)) return key;

+    return key + '=' + encodeURIComponent(String.interpret(value));

+  }

+

+  return {

+    initialize: function(object) {

+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);

+    },

+

+    _each: function(iterator) {

+      for (var key in this._object) {

+        var value = this._object[key], pair = [key, value];

+        pair.key = key;

+        pair.value = value;

+        iterator(pair);

+      }

+    },

+

+    set: function(key, value) {

+      return this._object[key] = value;

+    },

+

+    get: function(key) {

+      return this._object[key];

+    },

+

+    unset: function(key) {

+      var value = this._object[key];

+      delete this._object[key];

+      return value;

+    },

+

+    toObject: function() {

+      return Object.clone(this._object);

+    },

+

+    keys: function() {

+      return this.pluck('key');

+    },

+

+    values: function() {

+      return this.pluck('value');

+    },

+

+    index: function(value) {

+      var match = this.detect(function(pair) {

+        return pair.value === value;

+      });

+      return match && match.key;

+    },

+

+    merge: function(object) {

+      return this.clone().update(object);

+    },

+

+    update: function(object) {

+      return new Hash(object).inject(this, function(result, pair) {

+        result.set(pair.key, pair.value);

+        return result;

+      });

+    },

+

+    toQueryString: function() {

+      return this.map(function(pair) {

+        var key = encodeURIComponent(pair.key), values = pair.value;

+

+        if (values && typeof values == 'object') {

+          if (Object.isArray(values))

+            return values.map(toQueryPair.curry(key)).join('&');

+        }

+        return toQueryPair(key, values);

+      }).join('&');

+    },

+

+    inspect: function() {

+      return '#<Hash:{' + this.map(function(pair) {

+        return pair.map(Object.inspect).join(': ');

+      }).join(', ') + '}>';

+    },

+

+    toJSON: function() {

+      return Object.toJSON(this.toObject());

+    },

+

+    clone: function() {

+      return new Hash(this);

+    }

+  }

+})());

+

+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;

+Hash.from = $H;

+var ObjectRange = Class.create(Enumerable, {

+  initialize: function(start, end, exclusive) {

+    this.start = start;

+    this.end = end;

+    this.exclusive = exclusive;

+  },

+

+  _each: function(iterator) {

+    var value = this.start;

+    while (this.include(value)) {

+      iterator(value);

+      value = value.succ();

+    }

+  },

+

+  include: function(value) {

+    if (value < this.start)

+      return false;

+    if (this.exclusive)

+      return value < this.end;

+    return value <= this.end;

+  }

+});

+

+var $R = function(start, end, exclusive) {

+  return new ObjectRange(start, end, exclusive);

+};

+

+var Ajax = {

+  getTransport: function() {

+    return Try.these(

+      function() {return new XMLHttpRequest()},

+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},

+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}

+    ) || false;

+  },

+

+  activeRequestCount: 0

+};

+

+Ajax.Responders = {

+  responders: [],

+

+  _each: function(iterator) {

+    this.responders._each(iterator);

+  },

+

+  register: function(responder) {

+    if (!this.include(responder))

+      this.responders.push(responder);

+  },

+

+  unregister: function(responder) {

+    this.responders = this.responders.without(responder);

+  },

+

+  dispatch: function(callback, request, transport, json) {

+    this.each(function(responder) {

+      if (Object.isFunction(responder[callback])) {

+        try {

+          responder[callback].apply(responder, [request, transport, json]);

+        } catch (e) { }

+      }

+    });

+  }

+};

+

+Object.extend(Ajax.Responders, Enumerable);

+

+Ajax.Responders.register({

+  onCreate:   function() { Ajax.activeRequestCount++ },

+  onComplete: function() { Ajax.activeRequestCount-- }

+});

+

+Ajax.Base = Class.create({

+  initialize: function(options) {

+    this.options = {

+      method:       'post',

+      asynchronous: true,

+      contentType:  'application/x-www-form-urlencoded',

+      encoding:     'UTF-8',

+      parameters:   '',

+      evalJSON:     true,

+      evalJS:       true

+    };

+    Object.extend(this.options, options || { });

+

+    this.options.method = this.options.method.toLowerCase();

+

+    if (Object.isString(this.options.parameters))

+      this.options.parameters = this.options.parameters.toQueryParams();

+    else if (Object.isHash(this.options.parameters))

+      this.options.parameters = this.options.parameters.toObject();

+  }

+});

+

+Ajax.Request = Class.create(Ajax.Base, {

+  _complete: false,

+

+  initialize: function($super, url, options) {

+    $super(options);

+    this.transport = Ajax.getTransport();

+    this.request(url);

+  },

+

+  request: function(url) {

+    this.url = url;

+    this.method = this.options.method;

+    var params = Object.clone(this.options.parameters);

+

+    if (!['get', 'post'].include(this.method)) {

+      // simulate other verbs over post

+      params['_method'] = this.method;

+      this.method = 'post';

+    }

+

+    this.parameters = params;

+

+    if (params = Object.toQueryString(params)) {

+      // when GET, append parameters to URL

+      if (this.method == 'get')

+        this.url += (this.url.include('?') ? '&' : '?') + params;

+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))

+        params += '&_=';

+    }

+

+    try {

+      var response = new Ajax.Response(this);

+      if (this.options.onCreate) this.options.onCreate(response);

+      Ajax.Responders.dispatch('onCreate', this, response);

+

+      this.transport.open(this.method.toUpperCase(), this.url,

+        this.options.asynchronous);

+

+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

+

+      this.transport.onreadystatechange = this.onStateChange.bind(this);

+      this.setRequestHeaders();

+

+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;

+      this.transport.send(this.body);

+

+      /* Force Firefox to handle ready state 4 for synchronous requests */

+      if (!this.options.asynchronous && this.transport.overrideMimeType)

+        this.onStateChange();

+

+    }

+    catch (e) {

+      this.dispatchException(e);

+    }

+  },

+

+  onStateChange: function() {

+    var readyState = this.transport.readyState;

+    if (readyState > 1 && !((readyState == 4) && this._complete))

+      this.respondToReadyState(this.transport.readyState);

+  },

+

+  setRequestHeaders: function() {

+    var headers = {

+      'X-Requested-With': 'XMLHttpRequest',

+      'X-Prototype-Version': Prototype.Version,

+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'

+    };

+

+    if (this.method == 'post') {

+      headers['Content-type'] = this.options.contentType +

+        (this.options.encoding ? '; charset=' + this.options.encoding : '');

+

+      /* Force "Connection: close" for older Mozilla browsers to work

+       * around a bug where XMLHttpRequest sends an incorrect

+       * Content-length header. See Mozilla Bugzilla #246651.

+       */

+      if (this.transport.overrideMimeType &&

+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)

+            headers['Connection'] = 'close';

+    }

+

+    // user-defined headers

+    if (typeof this.options.requestHeaders == 'object') {

+      var extras = this.options.requestHeaders;

+

+      if (Object.isFunction(extras.push))

+        for (var i = 0, length = extras.length; i < length; i += 2)

+          headers[extras[i]] = extras[i+1];

+      else

+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });

+    }

+

+    for (var name in headers)

+      this.transport.setRequestHeader(name, headers[name]);

+  },

+

+  success: function() {

+    var status = this.getStatus();

+    return !status || (status >= 200 && status < 300);

+  },

+

+  getStatus: function() {

+    try {

+      return this.transport.status || 0;

+    } catch (e) { return 0 }

+  },

+

+  respondToReadyState: function(readyState) {

+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

+

+    if (state == 'Complete') {

+      try {

+        this._complete = true;

+        (this.options['on' + response.status]

+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]

+         || Prototype.emptyFunction)(response, response.headerJSON);

+      } catch (e) {

+        this.dispatchException(e);

+      }

+

+      var contentType = response.getHeader('Content-type');

+      if (this.options.evalJS == 'force'

+          || (this.options.evalJS && this.isSameOrigin() && contentType

+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))

+        this.evalResponse();

+    }

+

+    try {

+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);

+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);

+    } catch (e) {

+      this.dispatchException(e);

+    }

+

+    if (state == 'Complete') {

+      // avoid memory leak in MSIE: clean up

+      this.transport.onreadystatechange = Prototype.emptyFunction;

+    }

+  },

+

+  isSameOrigin: function() {

+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);

+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({

+      protocol: location.protocol,

+      domain: document.domain,

+      port: location.port ? ':' + location.port : ''

+    }));

+  },

+

+  getHeader: function(name) {

+    try {

+      return this.transport.getResponseHeader(name) || null;

+    } catch (e) { return null }

+  },

+

+  evalResponse: function() {

+    try {

+      return eval((this.transport.responseText || '').unfilterJSON());

+    } catch (e) {

+      this.dispatchException(e);

+    }

+  },

+

+  dispatchException: function(exception) {

+    (this.options.onException || Prototype.emptyFunction)(this, exception);

+    Ajax.Responders.dispatch('onException', this, exception);

+  }

+});

+

+Ajax.Request.Events =

+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

+

+Ajax.Response = Class.create({

+  initialize: function(request){

+    this.request = request;

+    var transport  = this.transport  = request.transport,

+        readyState = this.readyState = transport.readyState;

+

+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {

+      this.status       = this.getStatus();

+      this.statusText   = this.getStatusText();

+      this.responseText = String.interpret(transport.responseText);

+      this.headerJSON   = this._getHeaderJSON();

+    }

+

+    if(readyState == 4) {

+      var xml = transport.responseXML;

+      this.responseXML  = Object.isUndefined(xml) ? null : xml;

+      this.responseJSON = this._getResponseJSON();

+    }

+  },

+

+  status:      0,

+  statusText: '',

+

+  getStatus: Ajax.Request.prototype.getStatus,

+

+  getStatusText: function() {

+    try {

+      return this.transport.statusText || '';

+    } catch (e) { return '' }

+  },

+

+  getHeader: Ajax.Request.prototype.getHeader,

+

+  getAllHeaders: function() {

+    try {

+      return this.getAllResponseHeaders();

+    } catch (e) { return null }

+  },

+

+  getResponseHeader: function(name) {

+    return this.transport.getResponseHeader(name);

+  },

+

+  getAllResponseHeaders: function() {

+    return this.transport.getAllResponseHeaders();

+  },

+

+  _getHeaderJSON: function() {

+    var json = this.getHeader('X-JSON');

+    if (!json) return null;

+    json = decodeURIComponent(escape(json));

+    try {

+      return json.evalJSON(this.request.options.sanitizeJSON ||

+        !this.request.isSameOrigin());

+    } catch (e) {

+      this.request.dispatchException(e);

+    }

+  },

+

+  _getResponseJSON: function() {

+    var options = this.request.options;

+    if (!options.evalJSON || (options.evalJSON != 'force' &&

+      !(this.getHeader('Content-type') || '').include('application/json')) ||

+        this.responseText.blank())

+          return null;

+    try {

+      return this.responseText.evalJSON(options.sanitizeJSON ||

+        !this.request.isSameOrigin());

+    } catch (e) {

+      this.request.dispatchException(e);

+    }

+  }

+});

+

+Ajax.Updater = Class.create(Ajax.Request, {

+  initialize: function($super, container, url, options) {

+    this.container = {

+      success: (container.success || container),

+      failure: (container.failure || (container.success ? null : container))

+    };

+

+    options = Object.clone(options);

+    var onComplete = options.onComplete;

+    options.onComplete = (function(response, json) {

+      this.updateContent(response.responseText);

+      if (Object.isFunction(onComplete)) onComplete(response, json);

+    }).bind(this);

+

+    $super(url, options);

+  },

+

+  updateContent: function(responseText) {

+    var receiver = this.container[this.success() ? 'success' : 'failure'],

+        options = this.options;

+

+    if (!options.evalScripts) responseText = responseText.stripScripts();

+

+    if (receiver = $(receiver)) {

+      if (options.insertion) {

+        if (Object.isString(options.insertion)) {

+          var insertion = { }; insertion[options.insertion] = responseText;

+          receiver.insert(insertion);

+        }

+        else options.insertion(receiver, responseText);

+      }

+      else receiver.update(responseText);

+    }

+  }

+});

+

+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {

+  initialize: function($super, container, url, options) {

+    $super(options);

+    this.onComplete = this.options.onComplete;

+

+    this.frequency = (this.options.frequency || 2);

+    this.decay = (this.options.decay || 1);

+

+    this.updater = { };

+    this.container = container;

+    this.url = url;

+

+    this.start();

+  },

+

+  start: function() {

+    this.options.onComplete = this.updateComplete.bind(this);

+    this.onTimerEvent();

+  },

+

+  stop: function() {

+    this.updater.options.onComplete = undefined;

+    clearTimeout(this.timer);

+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);

+  },

+

+  updateComplete: function(response) {

+    if (this.options.decay) {

+      this.decay = (response.responseText == this.lastText ?

+        this.decay * this.options.decay : 1);

+

+      this.lastText = response.responseText;

+    }

+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);

+  },

+

+  onTimerEvent: function() {

+    this.updater = new Ajax.Updater(this.container, this.url, this.options);

+  }

+});

+function $(element) {

+  if (arguments.length > 1) {

+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)

+      elements.push($(arguments[i]));

+    return elements;

+  }

+  if (Object.isString(element))

+    element = document.getElementById(element);

+  return Element.extend(element);

+}

+

+if (Prototype.BrowserFeatures.XPath) {

+  document._getElementsByXPath = function(expression, parentElement) {

+    var results = [];

+    var query = document.evaluate(expression, $(parentElement) || document,

+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

+    for (var i = 0, length = query.snapshotLength; i < length; i++)

+      results.push(Element.extend(query.snapshotItem(i)));

+    return results;

+  };

+}

+

+/*--------------------------------------------------------------------------*/

+

+if (!window.Node) var Node = { };

+

+if (!Node.ELEMENT_NODE) {

+  // DOM level 2 ECMAScript Language Binding

+  Object.extend(Node, {

+    ELEMENT_NODE: 1,

+    ATTRIBUTE_NODE: 2,

+    TEXT_NODE: 3,

+    CDATA_SECTION_NODE: 4,

+    ENTITY_REFERENCE_NODE: 5,

+    ENTITY_NODE: 6,

+    PROCESSING_INSTRUCTION_NODE: 7,

+    COMMENT_NODE: 8,

+    DOCUMENT_NODE: 9,

+    DOCUMENT_TYPE_NODE: 10,

+    DOCUMENT_FRAGMENT_NODE: 11,

+    NOTATION_NODE: 12

+  });

+}

+

+(function() {

+  var element = this.Element;

+  this.Element = function(tagName, attributes) {

+    attributes = attributes || { };

+    tagName = tagName.toLowerCase();

+    var cache = Element.cache;

+    if (Prototype.Browser.IE && attributes.name) {

+      tagName = '<' + tagName + ' name="' + attributes.name + '">';

+      delete attributes.name;

+      return Element.writeAttribute(document.createElement(tagName), attributes);

+    }

+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));

+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);

+  };

+  Object.extend(this.Element, element || { });

+}).call(window);

+

+Element.cache = { };

+

+Element.Methods = {

+  visible: function(element) {

+    return $(element).style.display != 'none';

+  },

+

+  toggle: function(element) {

+    element = $(element);

+    Element[Element.visible(element) ? 'hide' : 'show'](element);

+    return element;

+  },

+

+  hide: function(element) {

+    $(element).style.display = 'none';

+    return element;

+  },

+

+  show: function(element) {

+    $(element).style.display = '';

+    return element;

+  },

+

+  remove: function(element) {

+    element = $(element);

+    element.parentNode.removeChild(element);

+    return element;

+  },

+

+  update: function(element, content) {

+    element = $(element);

+    if (content && content.toElement) content = content.toElement();

+    if (Object.isElement(content)) return element.update().insert(content);

+    content = Object.toHTML(content);

+    element.innerHTML = content.stripScripts();

+    content.evalScripts.bind(content).defer();

+    return element;

+  },

+

+  replace: function(element, content) {

+    element = $(element);

+    if (content && content.toElement) content = content.toElement();

+    else if (!Object.isElement(content)) {

+      content = Object.toHTML(content);

+      var range = element.ownerDocument.createRange();

+      range.selectNode(element);

+      content.evalScripts.bind(content).defer();

+      content = range.createContextualFragment(content.stripScripts());

+    }

+    element.parentNode.replaceChild(content, element);

+    return element;

+  },

+

+  insert: function(element, insertions) {

+    element = $(element);

+

+    if (Object.isString(insertions) || Object.isNumber(insertions) ||

+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))

+          insertions = {bottom:insertions};

+

+    var content, insert, tagName, childNodes;

+

+    for (var position in insertions) {

+      content  = insertions[position];

+      position = position.toLowerCase();

+      insert = Element._insertionTranslations[position];

+

+      if (content && content.toElement) content = content.toElement();

+      if (Object.isElement(content)) {

+        insert(element, content);

+        continue;

+      }

+

+      content = Object.toHTML(content);

+

+      tagName = ((position == 'before' || position == 'after')

+        ? element.parentNode : element).tagName.toUpperCase();

+

+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

+

+      if (position == 'top' || position == 'after') childNodes.reverse();

+      childNodes.each(insert.curry(element));

+

+      content.evalScripts.bind(content).defer();

+    }

+

+    return element;

+  },

+

+  wrap: function(element, wrapper, attributes) {

+    element = $(element);

+    if (Object.isElement(wrapper))

+      $(wrapper).writeAttribute(attributes || { });

+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);

+    else wrapper = new Element('div', wrapper);

+    if (element.parentNode)

+      element.parentNode.replaceChild(wrapper, element);

+    wrapper.appendChild(element);

+    return wrapper;

+  },

+

+  inspect: function(element) {

+    element = $(element);

+    var result = '<' + element.tagName.toLowerCase();

+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {

+      var property = pair.first(), attribute = pair.last();

+      var value = (element[property] || '').toString();

+      if (value) result += ' ' + attribute + '=' + value.inspect(true);

+    });

+    return result + '>';

+  },

+

+  recursivelyCollect: function(element, property) {

+    element = $(element);

+    var elements = [];

+    while (element = element[property])

+      if (element.nodeType == 1)

+        elements.push(Element.extend(element));

+    return elements;

+  },

+

+  ancestors: function(element) {

+    return $(element).recursivelyCollect('parentNode');

+  },

+

+  descendants: function(element) {

+    return $(element).select("*");

+  },

+

+  firstDescendant: function(element) {

+    element = $(element).firstChild;

+    while (element && element.nodeType != 1) element = element.nextSibling;

+    return $(element);

+  },

+

+  immediateDescendants: function(element) {

+    if (!(element = $(element).firstChild)) return [];

+    while (element && element.nodeType != 1) element = element.nextSibling;

+    if (element) return [element].concat($(element).nextSiblings());

+    return [];

+  },

+

+  previousSiblings: function(element) {

+    return $(element).recursivelyCollect('previousSibling');

+  },

+

+  nextSiblings: function(element) {

+    return $(element).recursivelyCollect('nextSibling');

+  },

+

+  siblings: function(element) {

+    element = $(element);

+    return element.previousSiblings().reverse().concat(element.nextSiblings());

+  },

+

+  match: function(element, selector) {

+    if (Object.isString(selector))

+      selector = new Selector(selector);

+    return selector.match($(element));

+  },

+

+  up: function(element, expression, index) {

+    element = $(element);

+    if (arguments.length == 1) return $(element.parentNode);

+    var ancestors = element.ancestors();

+    return Object.isNumber(expression) ? ancestors[expression] :

+      Selector.findElement(ancestors, expression, index);

+  },

+

+  down: function(element, expression, index) {

+    element = $(element);

+    if (arguments.length == 1) return element.firstDescendant();

+    return Object.isNumber(expression) ? element.descendants()[expression] :

+      element.select(expression)[index || 0];

+  },

+

+  previous: function(element, expression, index) {

+    element = $(element);

+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));

+    var previousSiblings = element.previousSiblings();

+    return Object.isNumber(expression) ? previousSiblings[expression] :

+      Selector.findElement(previousSiblings, expression, index);

+  },

+

+  next: function(element, expression, index) {

+    element = $(element);

+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));

+    var nextSiblings = element.nextSiblings();

+    return Object.isNumber(expression) ? nextSiblings[expression] :

+      Selector.findElement(nextSiblings, expression, index);

+  },

+

+  select: function() {

+    var args = $A(arguments), element = $(args.shift());

+    return Selector.findChildElements(element, args);

+  },

+

+  adjacent: function() {

+    var args = $A(arguments), element = $(args.shift());

+    return Selector.findChildElements(element.parentNode, args).without(element);

+  },

+

+  identify: function(element) {

+    element = $(element);

+    var id = element.readAttribute('id'), self = arguments.callee;

+    if (id) return id;

+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));

+    element.writeAttribute('id', id);

+    return id;

+  },

+

+  readAttribute: function(element, name) {

+    element = $(element);

+    if (Prototype.Browser.IE) {

+      var t = Element._attributeTranslations.read;

+      if (t.values[name]) return t.values[name](element, name);

+      if (t.names[name]) name = t.names[name];

+      if (name.include(':')) {

+        return (!element.attributes || !element.attributes[name]) ? null :

+         element.attributes[name].value;

+      }

+    }

+    return element.getAttribute(name);

+  },

+

+  writeAttribute: function(element, name, value) {

+    element = $(element);

+    var attributes = { }, t = Element._attributeTranslations.write;

+

+    if (typeof name == 'object') attributes = name;

+    else attributes[name] = Object.isUndefined(value) ? true : value;

+

+    for (var attr in attributes) {

+      name = t.names[attr] || attr;

+      value = attributes[attr];

+      if (t.values[attr]) name = t.values[attr](element, value);

+      if (value === false || value === null)

+        element.removeAttribute(name);

+      else if (value === true)

+        element.setAttribute(name, name);

+      else element.setAttribute(name, value);

+    }

+    return element;

+  },

+

+  getHeight: function(element) {

+    return $(element).getDimensions().height;

+  },

+

+  getWidth: function(element) {

+    return $(element).getDimensions().width;

+  },

+

+  classNames: function(element) {

+    return new Element.ClassNames(element);

+  },

+

+  hasClassName: function(element, className) {

+    if (!(element = $(element))) return;

+    var elementClassName = element.className;

+    return (elementClassName.length > 0 && (elementClassName == className ||

+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));

+  },

+

+  addClassName: function(element, className) {

+    if (!(element = $(element))) return;

+    if (!element.hasClassName(className))

+      element.className += (element.className ? ' ' : '') + className;

+    return element;

+  },

+

+  removeClassName: function(element, className) {

+    if (!(element = $(element))) return;

+    element.className = element.className.replace(

+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();

+    return element;

+  },

+

+  toggleClassName: function(element, className) {

+    if (!(element = $(element))) return;

+    return element[element.hasClassName(className) ?

+      'removeClassName' : 'addClassName'](className);

+  },

+

+  // removes whitespace-only text node children

+  cleanWhitespace: function(element) {

+    element = $(element);

+    var node = element.firstChild;

+    while (node) {

+      var nextNode = node.nextSibling;

+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))

+        element.removeChild(node);

+      node = nextNode;

+    }

+    return element;

+  },

+

+  empty: function(element) {

+    return $(element).innerHTML.blank();

+  },

+

+  descendantOf: function(element, ancestor) {

+    element = $(element), ancestor = $(ancestor);

+    var originalAncestor = ancestor;

+

+    if (element.compareDocumentPosition)

+      return (element.compareDocumentPosition(ancestor) & 8) === 8;

+

+    if (element.sourceIndex && !Prototype.Browser.Opera) {

+      var e = element.sourceIndex, a = ancestor.sourceIndex,

+       nextAncestor = ancestor.nextSibling;

+      if (!nextAncestor) {

+        do { ancestor = ancestor.parentNode; }

+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);

+      }

+      if (nextAncestor && nextAncestor.sourceIndex)

+       return (e > a && e < nextAncestor.sourceIndex);

+    }

+

+    while (element = element.parentNode)

+      if (element == originalAncestor) return true;

+    return false;

+  },

+

+  scrollTo: function(element) {

+    element = $(element);

+    var pos = element.cumulativeOffset();

+    window.scrollTo(pos[0], pos[1]);

+    return element;

+  },

+

+  getStyle: function(element, style) {

+    element = $(element);

+    style = style == 'float' ? 'cssFloat' : style.camelize();

+    var value = element.style[style];

+    if (!value) {

+      var css = document.defaultView.getComputedStyle(element, null);

+      value = css ? css[style] : null;

+    }

+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;

+    return value == 'auto' ? null : value;

+  },

+

+  getOpacity: function(element) {

+    return $(element).getStyle('opacity');

+  },

+

+  setStyle: function(element, styles) {

+    element = $(element);

+    var elementStyle = element.style, match;

+    if (Object.isString(styles)) {

+      element.style.cssText += ';' + styles;

+      return styles.include('opacity') ?

+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;

+    }

+    for (var property in styles)

+      if (property == 'opacity') element.setOpacity(styles[property]);

+      else

+        elementStyle[(property == 'float' || property == 'cssFloat') ?

+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :

+            property] = styles[property];

+

+    return element;

+  },

+

+  setOpacity: function(element, value) {

+    element = $(element);

+    element.style.opacity = (value == 1 || value === '') ? '' :

+      (value < 0.00001) ? 0 : value;

+    return element;

+  },

+

+  getDimensions: function(element) {

+    element = $(element);

+    var display = $(element).getStyle('display');

+    if (display != 'none' && display != null) // Safari bug

+      return {width: element.offsetWidth, height: element.offsetHeight};

+

+    // All *Width and *Height properties give 0 on elements with display none,

+    // so enable the element temporarily

+    var els = element.style;

+    var originalVisibility = els.visibility;

+    var originalPosition = els.position;

+    var originalDisplay = els.display;

+    els.visibility = 'hidden';

+    els.position = 'absolute';

+    els.display = 'block';

+    var originalWidth = element.clientWidth;

+    var originalHeight = element.clientHeight;

+    els.display = originalDisplay;

+    els.position = originalPosition;

+    els.visibility = originalVisibility;

+    return {width: originalWidth, height: originalHeight};

+  },

+

+  makePositioned: function(element) {

+    element = $(element);

+    var pos = Element.getStyle(element, 'position');

+    if (pos == 'static' || !pos) {

+      element._madePositioned = true;

+      element.style.position = 'relative';

+      // Opera returns the offset relative to the positioning context, when an

+      // element is position relative but top and left have not been defined

+      if (window.opera) {

+        element.style.top = 0;

+        element.style.left = 0;

+      }

+    }

+    return element;

+  },

+

+  undoPositioned: function(element) {

+    element = $(element);

+    if (element._madePositioned) {

+      element._madePositioned = undefined;

+      element.style.position =

+        element.style.top =

+        element.style.left =

+        element.style.bottom =

+        element.style.right = '';

+    }

+    return element;

+  },

+

+  makeClipping: function(element) {

+    element = $(element);

+    if (element._overflow) return element;

+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';

+    if (element._overflow !== 'hidden')

+      element.style.overflow = 'hidden';

+    return element;

+  },

+

+  undoClipping: function(element) {

+    element = $(element);

+    if (!element._overflow) return element;

+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;

+    element._overflow = null;

+    return element;

+  },

+

+  cumulativeOffset: function(element) {

+    var valueT = 0, valueL = 0;

+    do {

+      valueT += element.offsetTop  || 0;

+      valueL += element.offsetLeft || 0;

+      element = element.offsetParent;

+    } while (element);

+    return Element._returnOffset(valueL, valueT);

+  },

+

+  positionedOffset: function(element) {

+    var valueT = 0, valueL = 0;

+    do {

+      valueT += element.offsetTop  || 0;

+      valueL += element.offsetLeft || 0;

+      element = element.offsetParent;

+      if (element) {

+        if (element.tagName == 'BODY') break;

+        var p = Element.getStyle(element, 'position');

+        if (p !== 'static') break;

+      }

+    } while (element);

+    return Element._returnOffset(valueL, valueT);

+  },

+

+  absolutize: function(element) {

+    element = $(element);

+    if (element.getStyle('position') == 'absolute') return;

+    // Position.prepare(); // To be done manually by Scripty when it needs it.

+

+    var offsets = element.positionedOffset();

+    var top     = offsets[1];

+    var left    = offsets[0];

+    var width   = element.clientWidth;

+    var height  = element.clientHeight;

+

+    element._originalLeft   = left - parseFloat(element.style.left  || 0);

+    element._originalTop    = top  - parseFloat(element.style.top || 0);

+    element._originalWidth  = element.style.width;

+    element._originalHeight = element.style.height;

+

+    element.style.position = 'absolute';

+    element.style.top    = top + 'px';

+    element.style.left   = left + 'px';

+    element.style.width  = width + 'px';

+    element.style.height = height + 'px';

+    return element;

+  },

+

+  relativize: function(element) {

+    element = $(element);

+    if (element.getStyle('position') == 'relative') return;

+    // Position.prepare(); // To be done manually by Scripty when it needs it.

+

+    element.style.position = 'relative';

+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);

+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

+

+    element.style.top    = top + 'px';

+    element.style.left   = left + 'px';

+    element.style.height = element._originalHeight;

+    element.style.width  = element._originalWidth;

+    return element;

+  },

+

+  cumulativeScrollOffset: function(element) {

+    var valueT = 0, valueL = 0;

+    do {

+      valueT += element.scrollTop  || 0;

+      valueL += element.scrollLeft || 0;

+      element = element.parentNode;

+    } while (element);

+    return Element._returnOffset(valueL, valueT);

+  },

+

+  getOffsetParent: function(element) {

+    if (element.offsetParent) return $(element.offsetParent);

+    if (element == document.body) return $(element);

+

+    while ((element = element.parentNode) && element != document.body)

+      if (Element.getStyle(element, 'position') != 'static')

+        return $(element);

+

+    return $(document.body);

+  },

+

+  viewportOffset: function(forElement) {

+    var valueT = 0, valueL = 0;

+

+    var element = forElement;

+    do {

+      valueT += element.offsetTop  || 0;

+      valueL += element.offsetLeft || 0;

+

+      // Safari fix

+      if (element.offsetParent == document.body &&

+        Element.getStyle(element, 'position') == 'absolute') break;

+

+    } while (element = element.offsetParent);

+

+    element = forElement;

+    do {

+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {

+        valueT -= element.scrollTop  || 0;

+        valueL -= element.scrollLeft || 0;

+      }

+    } while (element = element.parentNode);

+

+    return Element._returnOffset(valueL, valueT);

+  },

+

+  clonePosition: function(element, source) {

+    var options = Object.extend({

+      setLeft:    true,

+      setTop:     true,

+      setWidth:   true,

+      setHeight:  true,

+      offsetTop:  0,

+      offsetLeft: 0

+    }, arguments[2] || { });

+

+    // find page position of source

+    source = $(source);

+    var p = source.viewportOffset();

+

+    // find coordinate system to use

+    element = $(element);

+    var delta = [0, 0];

+    var parent = null;

+    // delta [0,0] will do fine with position: fixed elements,

+    // position:absolute needs offsetParent deltas

+    if (Element.getStyle(element, 'position') == 'absolute') {

+      parent = element.getOffsetParent();

+      delta = parent.viewportOffset();

+    }

+

+    // correct by body offsets (fixes Safari)

+    if (parent == document.body) {

+      delta[0] -= document.body.offsetLeft;

+      delta[1] -= document.body.offsetTop;

+    }

+

+    // set position

+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';

+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';

+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';

+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';

+    return element;

+  }

+};

+

+Element.Methods.identify.counter = 1;

+

+Object.extend(Element.Methods, {

+  getElementsBySelector: Element.Methods.select,

+  childElements: Element.Methods.immediateDescendants

+});

+

+Element._attributeTranslations = {

+  write: {

+    names: {

+      className: 'class',

+      htmlFor:   'for'

+    },

+    values: { }

+  }

+};

+

+if (Prototype.Browser.Opera) {

+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(

+    function(proceed, element, style) {

+      switch (style) {

+        case 'left': case 'top': case 'right': case 'bottom':

+          if (proceed(element, 'position') === 'static') return null;

+        case 'height': case 'width':

+          // returns '0px' for hidden elements; we want it to return null

+          if (!Element.visible(element)) return null;

+

+          // returns the border-box dimensions rather than the content-box

+          // dimensions, so we subtract padding and borders from the value

+          var dim = parseInt(proceed(element, style), 10);

+

+          if (dim !== element['offset' + style.capitalize()])

+            return dim + 'px';

+

+          var properties;

+          if (style === 'height') {

+            properties = ['border-top-width', 'padding-top',

+             'padding-bottom', 'border-bottom-width'];

+          }

+          else {

+            properties = ['border-left-width', 'padding-left',

+             'padding-right', 'border-right-width'];

+          }

+          return properties.inject(dim, function(memo, property) {

+            var val = proceed(element, property);

+            return val === null ? memo : memo - parseInt(val, 10);

+          }) + 'px';

+        default: return proceed(element, style);

+      }

+    }

+  );

+

+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(

+    function(proceed, element, attribute) {

+      if (attribute === 'title') return element.title;

+      return proceed(element, attribute);

+    }

+  );

+}

+

+else if (Prototype.Browser.IE) {

+  // IE doesn't report offsets correctly for static elements, so we change them

+  // to "relative" to get the values, then change them back.

+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(

+    function(proceed, element) {

+      element = $(element);

+      var position = element.getStyle('position');

+      if (position !== 'static') return proceed(element);

+      element.setStyle({ position: 'relative' });

+      var value = proceed(element);

+      element.setStyle({ position: position });

+      return value;

+    }

+  );

+

+  $w('positionedOffset viewportOffset').each(function(method) {

+    Element.Methods[method] = Element.Methods[method].wrap(

+      function(proceed, element) {

+        element = $(element);

+        var position = element.getStyle('position');

+        if (position !== 'static') return proceed(element);

+        // Trigger hasLayout on the offset parent so that IE6 reports

+        // accurate offsetTop and offsetLeft values for position: fixed.

+        var offsetParent = element.getOffsetParent();

+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')

+          offsetParent.setStyle({ zoom: 1 });

+        element.setStyle({ position: 'relative' });

+        var value = proceed(element);

+        element.setStyle({ position: position });

+        return value;

+      }

+    );

+  });

+

+  Element.Methods.getStyle = function(element, style) {

+    element = $(element);

+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();

+    var value = element.style[style];

+    if (!value && element.currentStyle) value = element.currentStyle[style];

+

+    if (style == 'opacity') {

+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))

+        if (value[1]) return parseFloat(value[1]) / 100;

+      return 1.0;

+    }

+

+    if (value == 'auto') {

+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))

+        return element['offset' + style.capitalize()] + 'px';

+      return null;

+    }

+    return value;

+  };

+

+  Element.Methods.setOpacity = function(element, value) {

+    function stripAlpha(filter){

+      return filter.replace(/alpha\([^\)]*\)/gi,'');

+    }

+    element = $(element);

+    var currentStyle = element.currentStyle;

+    if ((currentStyle && !currentStyle.hasLayout) ||

+      (!currentStyle && element.style.zoom == 'normal'))

+        element.style.zoom = 1;

+

+    var filter = element.getStyle('filter'), style = element.style;

+    if (value == 1 || value === '') {

+      (filter = stripAlpha(filter)) ?

+        style.filter = filter : style.removeAttribute('filter');

+      return element;

+    } else if (value < 0.00001) value = 0;

+    style.filter = stripAlpha(filter) +

+      'alpha(opacity=' + (value * 100) + ')';

+    return element;

+  };

+

+  Element._attributeTranslations = {

+    read: {

+      names: {

+        'class': 'className',

+        'for':   'htmlFor'

+      },

+      values: {

+        _getAttr: function(element, attribute) {

+          return element.getAttribute(attribute, 2);

+        },

+        _getAttrNode: function(element, attribute) {

+          var node = element.getAttributeNode(attribute);

+          return node ? node.value : "";

+        },

+        _getEv: function(element, attribute) {

+          attribute = element.getAttribute(attribute);

+          return attribute ? attribute.toString().slice(23, -2) : null;

+        },

+        _flag: function(element, attribute) {

+          return $(element).hasAttribute(attribute) ? attribute : null;

+        },

+        style: function(element) {

+          return element.style.cssText.toLowerCase();

+        },

+        title: function(element) {

+          return element.title;

+        }

+      }

+    }

+  };

+

+  Element._attributeTranslations.write = {

+    names: Object.extend({

+      cellpadding: 'cellPadding',

+      cellspacing: 'cellSpacing'

+    }, Element._attributeTranslations.read.names),

+    values: {

+      checked: function(element, value) {

+        element.checked = !!value;

+      },

+

+      style: function(element, value) {

+        element.style.cssText = value ? value : '';

+      }

+    }

+  };

+

+  Element._attributeTranslations.has = {};

+

+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +

+      'encType maxLength readOnly longDesc').each(function(attr) {

+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;

+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;

+  });

+

+  (function(v) {

+    Object.extend(v, {

+      href:        v._getAttr,

+      src:         v._getAttr,

+      type:        v._getAttr,

+      action:      v._getAttrNode,

+      disabled:    v._flag,

+      checked:     v._flag,

+      readonly:    v._flag,

+      multiple:    v._flag,

+      onload:      v._getEv,

+      onunload:    v._getEv,

+      onclick:     v._getEv,

+      ondblclick:  v._getEv,

+      onmousedown: v._getEv,

+      onmouseup:   v._getEv,

+      onmouseover: v._getEv,

+      onmousemove: v._getEv,

+      onmouseout:  v._getEv,

+      onfocus:     v._getEv,

+      onblur:      v._getEv,

+      onkeypress:  v._getEv,

+      onkeydown:   v._getEv,

+      onkeyup:     v._getEv,

+      onsubmit:    v._getEv,

+      onreset:     v._getEv,

+      onselect:    v._getEv,

+      onchange:    v._getEv

+    });

+  })(Element._attributeTranslations.read.values);

+}

+

+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {

+  Element.Methods.setOpacity = function(element, value) {

+    element = $(element);

+    element.style.opacity = (value == 1) ? 0.999999 :

+      (value === '') ? '' : (value < 0.00001) ? 0 : value;

+    return element;

+  };

+}

+

+else if (Prototype.Browser.WebKit) {

+  Element.Methods.setOpacity = function(element, value) {

+    element = $(element);

+    element.style.opacity = (value == 1 || value === '') ? '' :

+      (value < 0.00001) ? 0 : value;

+

+    if (value == 1)

+      if(element.tagName == 'IMG' && element.width) {

+        element.width++; element.width--;

+      } else try {

+        var n = document.createTextNode(' ');

+        element.appendChild(n);

+        element.removeChild(n);

+      } catch (e) { }

+

+    return element;

+  };

+

+  // Safari returns margins on body which is incorrect if the child is absolutely

+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for

+  // KHTML/WebKit only.

+  Element.Methods.cumulativeOffset = function(element) {

+    var valueT = 0, valueL = 0;

+    do {

+      valueT += element.offsetTop  || 0;

+      valueL += element.offsetLeft || 0;

+      if (element.offsetParent == document.body)

+        if (Element.getStyle(element, 'position') == 'absolute') break;

+

+      element = element.offsetParent;

+    } while (element);

+

+    return Element._returnOffset(valueL, valueT);

+  };

+}

+

+if (Prototype.Browser.IE || Prototype.Browser.Opera) {

+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements

+  Element.Methods.update = function(element, content) {

+    element = $(element);

+

+    if (content && content.toElement) content = content.toElement();

+    if (Object.isElement(content)) return element.update().insert(content);

+

+    content = Object.toHTML(content);

+    var tagName = element.tagName.toUpperCase();

+

+    if (tagName in Element._insertionTranslations.tags) {

+      $A(element.childNodes).each(function(node) { element.removeChild(node) });

+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())

+        .each(function(node) { element.appendChild(node) });

+    }

+    else element.innerHTML = content.stripScripts();

+

+    content.evalScripts.bind(content).defer();

+    return element;

+  };

+}

+

+if ('outerHTML' in document.createElement('div')) {

+  Element.Methods.replace = function(element, content) {

+    element = $(element);

+

+    if (content && content.toElement) content = content.toElement();

+    if (Object.isElement(content)) {

+      element.parentNode.replaceChild(content, element);

+      return element;

+    }

+

+    content = Object.toHTML(content);

+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

+

+    if (Element._insertionTranslations.tags[tagName]) {

+      var nextSibling = element.next();

+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

+      parent.removeChild(element);

+      if (nextSibling)

+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });

+      else

+        fragments.each(function(node) { parent.appendChild(node) });

+    }

+    else element.outerHTML = content.stripScripts();

+

+    content.evalScripts.bind(content).defer();

+    return element;

+  };

+}

+

+Element._returnOffset = function(l, t) {

+  var result = [l, t];

+  result.left = l;

+  result.top = t;

+  return result;

+};

+

+Element._getContentFromAnonymousElement = function(tagName, html) {

+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];

+  if (t) {

+    div.innerHTML = t[0] + html + t[1];

+    t[2].times(function() { div = div.firstChild });

+  } else div.innerHTML = html;

+  return $A(div.childNodes);

+};

+

+Element._insertionTranslations = {

+  before: function(element, node) {

+    element.parentNode.insertBefore(node, element);

+  },

+  top: function(element, node) {

+    element.insertBefore(node, element.firstChild);

+  },

+  bottom: function(element, node) {

+    element.appendChild(node);

+  },

+  after: function(element, node) {

+    element.parentNode.insertBefore(node, element.nextSibling);

+  },

+  tags: {

+    TABLE:  ['<table>',                '</table>',                   1],

+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],

+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],

+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],

+    SELECT: ['<select>',               '</select>',                  1]

+  }

+};

+

+(function() {

+  Object.extend(this.tags, {

+    THEAD: this.tags.TBODY,

+    TFOOT: this.tags.TBODY,

+    TH:    this.tags.TD

+  });

+}).call(Element._insertionTranslations);

+

+Element.Methods.Simulated = {

+  hasAttribute: function(element, attribute) {

+    attribute = Element._attributeTranslations.has[attribute] || attribute;

+    var node = $(element).getAttributeNode(attribute);

+    return node && node.specified;

+  }

+};

+

+Element.Methods.ByTag = { };

+

+Object.extend(Element, Element.Methods);

+

+if (!Prototype.BrowserFeatures.ElementExtensions &&

+    document.createElement('div').__proto__) {

+  window.HTMLElement = { };

+  window.HTMLElement.prototype = document.createElement('div').__proto__;

+  Prototype.BrowserFeatures.ElementExtensions = true;

+}

+

+Element.extend = (function() {

+  if (Prototype.BrowserFeatures.SpecificElementExtensions)

+    return Prototype.K;

+

+  var Methods = { }, ByTag = Element.Methods.ByTag;

+

+  var extend = Object.extend(function(element) {

+    if (!element || element._extendedByPrototype ||

+        element.nodeType != 1 || element == window) return element;

+

+    var methods = Object.clone(Methods),

+      tagName = element.tagName, property, value;

+

+    // extend methods for specific tags

+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

+

+    for (property in methods) {

+      value = methods[property];

+      if (Object.isFunction(value) && !(property in element))

+        element[property] = value.methodize();

+    }

+

+    element._extendedByPrototype = Prototype.emptyFunction;

+    return element;

+

+  }, {

+    refresh: function() {

+      // extend methods for all tags (Safari doesn't need this)

+      if (!Prototype.BrowserFeatures.ElementExtensions) {

+        Object.extend(Methods, Element.Methods);

+        Object.extend(Methods, Element.Methods.Simulated);

+      }

+    }

+  });

+

+  extend.refresh();

+  return extend;

+})();

+

+Element.hasAttribute = function(element, attribute) {

+  if (element.hasAttribute) return element.hasAttribute(attribute);

+  return Element.Methods.Simulated.hasAttribute(element, attribute);

+};

+

+Element.addMethods = function(methods) {

+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

+

+  if (!methods) {

+    Object.extend(Form, Form.Methods);

+    Object.extend(Form.Element, Form.Element.Methods);

+    Object.extend(Element.Methods.ByTag, {

+      "FORM":     Object.clone(Form.Methods),

+      "INPUT":    Object.clone(Form.Element.Methods),

+      "SELECT":   Object.clone(Form.Element.Methods),

+      "TEXTAREA": Object.clone(Form.Element.Methods)

+    });

+  }

+

+  if (arguments.length == 2) {

+    var tagName = methods;

+    methods = arguments[1];

+  }

+

+  if (!tagName) Object.extend(Element.Methods, methods || { });

+  else {

+    if (Object.isArray(tagName)) tagName.each(extend);

+    else extend(tagName);

+  }

+

+  function extend(tagName) {

+    tagName = tagName.toUpperCase();

+    if (!Element.Methods.ByTag[tagName])

+      Element.Methods.ByTag[tagName] = { };

+    Object.extend(Element.Methods.ByTag[tagName], methods);

+  }

+

+  function copy(methods, destination, onlyIfAbsent) {

+    onlyIfAbsent = onlyIfAbsent || false;

+    for (var property in methods) {

+      var value = methods[property];

+      if (!Object.isFunction(value)) continue;

+      if (!onlyIfAbsent || !(property in destination))

+        destination[property] = value.methodize();

+    }

+  }

+

+  function findDOMClass(tagName) {

+    var klass;

+    var trans = {

+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",

+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",

+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",

+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",

+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":

+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":

+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":

+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":

+      "FrameSet", "IFRAME": "IFrame"

+    };

+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';

+    if (window[klass]) return window[klass];

+    klass = 'HTML' + tagName + 'Element';

+    if (window[klass]) return window[klass];

+    klass = 'HTML' + tagName.capitalize() + 'Element';

+    if (window[klass]) return window[klass];

+

+    window[klass] = { };

+    window[klass].prototype = document.createElement(tagName).__proto__;

+    return window[klass];

+  }

+

+  if (F.ElementExtensions) {

+    copy(Element.Methods, HTMLElement.prototype);

+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);

+  }

+

+  if (F.SpecificElementExtensions) {

+    for (var tag in Element.Methods.ByTag) {

+      var klass = findDOMClass(tag);

+      if (Object.isUndefined(klass)) continue;

+      copy(T[tag], klass.prototype);

+    }

+  }

+

+  Object.extend(Element, Element.Methods);

+  delete Element.ByTag;

+

+  if (Element.extend.refresh) Element.extend.refresh();

+  Element.cache = { };

+};

+

+document.viewport = {

+  getDimensions: function() {

+    var dimensions = { };

+    var B = Prototype.Browser;

+    $w('width height').each(function(d) {

+      var D = d.capitalize();

+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :

+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];

+    });

+    return dimensions;

+  },

+

+  getWidth: function() {

+    return this.getDimensions().width;

+  },

+

+  getHeight: function() {

+    return this.getDimensions().height;

+  },

+

+  getScrollOffsets: function() {

+    return Element._returnOffset(

+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,

+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);

+  }

+};

+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,

+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style

+ * license.  Please see http://www.yui-ext.com/ for more information. */

+

+var Selector = Class.create({

+  initialize: function(expression) {

+    this.expression = expression.strip();

+    this.compileMatcher();

+  },

+

+  shouldUseXPath: function() {

+    if (!Prototype.BrowserFeatures.XPath) return false;

+

+    var e = this.expression;

+

+    // Safari 3 chokes on :*-of-type and :empty

+    if (Prototype.Browser.WebKit &&

+     (e.include("-of-type") || e.include(":empty")))

+      return false;

+

+    // XPath can't do namespaced attributes, nor can it read

+    // the "checked" property from DOM nodes

+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))

+      return false;

+

+    return true;

+  },

+

+  compileMatcher: function() {

+    if (this.shouldUseXPath())

+      return this.compileXPathMatcher();

+

+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,

+        c = Selector.criteria, le, p, m;

+

+    if (Selector._cache[e]) {

+      this.matcher = Selector._cache[e];

+      return;

+    }

+

+    this.matcher = ["this.matcher = function(root) {",

+                    "var r = root, h = Selector.handlers, c = false, n;"];

+

+    while (e && le != e && (/\S/).test(e)) {

+      le = e;

+      for (var i in ps) {

+        p = ps[i];

+        if (m = e.match(p)) {

+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :

+    	      new Template(c[i]).evaluate(m));

+          e = e.replace(m[0], '');

+          break;

+        }

+      }

+    }

+

+    this.matcher.push("return h.unique(n);\n}");

+    eval(this.matcher.join('\n'));

+    Selector._cache[this.expression] = this.matcher;

+  },

+

+  compileXPathMatcher: function() {

+    var e = this.expression, ps = Selector.patterns,

+        x = Selector.xpath, le, m;

+

+    if (Selector._cache[e]) {

+      this.xpath = Selector._cache[e]; return;

+    }

+

+    this.matcher = ['.//*'];

+    while (e && le != e && (/\S/).test(e)) {

+      le = e;

+      for (var i in ps) {

+        if (m = e.match(ps[i])) {

+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :

+            new Template(x[i]).evaluate(m));

+          e = e.replace(m[0], '');

+          break;

+        }

+      }

+    }

+

+    this.xpath = this.matcher.join('');

+    Selector._cache[this.expression] = this.xpath;

+  },

+

+  findElements: function(root) {

+    root = root || document;

+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);

+    return this.matcher(root);

+  },

+

+  match: function(element) {

+    this.tokens = [];

+

+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;

+    var le, p, m;

+

+    while (e && le !== e && (/\S/).test(e)) {

+      le = e;

+      for (var i in ps) {

+        p = ps[i];

+        if (m = e.match(p)) {

+          // use the Selector.assertions methods unless the selector

+          // is too complex.

+          if (as[i]) {

+            this.tokens.push([i, Object.clone(m)]);

+            e = e.replace(m[0], '');

+          } else {

+            // reluctantly do a document-wide search

+            // and look for a match in the array

+            return this.findElements(document).include(element);

+          }

+        }

+      }

+    }

+

+    var match = true, name, matches;

+    for (var i = 0, token; token = this.tokens[i]; i++) {

+      name = token[0], matches = token[1];

+      if (!Selector.assertions[name](element, matches)) {

+        match = false; break;

+      }

+    }

+

+    return match;

+  },

+

+  toString: function() {

+    return this.expression;

+  },

+

+  inspect: function() {

+    return "#<Selector:" + this.expression.inspect() + ">";

+  }

+});

+

+Object.extend(Selector, {

+  _cache: { },

+

+  xpath: {

+    descendant:   "//*",

+    child:        "/*",

+    adjacent:     "/following-sibling::*[1]",

+    laterSibling: '/following-sibling::*',

+    tagName:      function(m) {

+      if (m[1] == '*') return '';

+      return "[local-name()='" + m[1].toLowerCase() +

+             "' or local-name()='" + m[1].toUpperCase() + "']";

+    },

+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",

+    id:           "[@id='#{1}']",

+    attrPresence: function(m) {

+      m[1] = m[1].toLowerCase();

+      return new Template("[@#{1}]").evaluate(m);

+    },

+    attr: function(m) {

+      m[1] = m[1].toLowerCase();

+      m[3] = m[5] || m[6];

+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);

+    },

+    pseudo: function(m) {

+      var h = Selector.xpath.pseudos[m[1]];

+      if (!h) return '';

+      if (Object.isFunction(h)) return h(m);

+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);

+    },

+    operators: {

+      '=':  "[@#{1}='#{3}']",

+      '!=': "[@#{1}!='#{3}']",

+      '^=': "[starts-with(@#{1}, '#{3}')]",

+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",

+      '*=': "[contains(@#{1}, '#{3}')]",

+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",

+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"

+    },

+    pseudos: {

+      'first-child': '[not(preceding-sibling::*)]',

+      'last-child':  '[not(following-sibling::*)]',

+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',

+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",

+      'checked':     "[@checked]",

+      'disabled':    "[@disabled]",

+      'enabled':     "[not(@disabled)]",

+      'not': function(m) {

+        var e = m[6], p = Selector.patterns,

+            x = Selector.xpath, le, v;

+

+        var exclusion = [];

+        while (e && le != e && (/\S/).test(e)) {

+          le = e;

+          for (var i in p) {

+            if (m = e.match(p[i])) {

+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);

+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");

+              e = e.replace(m[0], '');

+              break;

+            }

+          }

+        }

+        return "[not(" + exclusion.join(" and ") + ")]";

+      },

+      'nth-child':      function(m) {

+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);

+      },

+      'nth-last-child': function(m) {

+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);

+      },

+      'nth-of-type':    function(m) {

+        return Selector.xpath.pseudos.nth("position() ", m);

+      },

+      'nth-last-of-type': function(m) {

+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);

+      },

+      'first-of-type':  function(m) {

+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);

+      },

+      'last-of-type':   function(m) {

+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);

+      },

+      'only-of-type':   function(m) {

+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);

+      },

+      nth: function(fragment, m) {

+        var mm, formula = m[6], predicate;

+        if (formula == 'even') formula = '2n+0';

+        if (formula == 'odd')  formula = '2n+1';

+        if (mm = formula.match(/^(\d+)$/)) // digit only

+          return '[' + fragment + "= " + mm[1] + ']';

+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b

+          if (mm[1] == "-") mm[1] = -1;

+          var a = mm[1] ? Number(mm[1]) : 1;

+          var b = mm[2] ? Number(mm[2]) : 0;

+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +

+          "((#{fragment} - #{b}) div #{a} >= 0)]";

+          return new Template(predicate).evaluate({

+            fragment: fragment, a: a, b: b });

+        }

+      }

+    }

+  },

+

+  criteria: {

+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',

+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',

+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',

+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',

+    attr: function(m) {

+      m[3] = (m[5] || m[6]);

+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);

+    },

+    pseudo: function(m) {

+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');

+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);

+    },

+    descendant:   'c = "descendant";',

+    child:        'c = "child";',

+    adjacent:     'c = "adjacent";',

+    laterSibling: 'c = "laterSibling";'

+  },

+

+  patterns: {

+    // combinators must be listed first

+    // (and descendant needs to be last combinator)

+    laterSibling: /^\s*~\s*/,

+    child:        /^\s*>\s*/,

+    adjacent:     /^\s*\+\s*/,

+    descendant:   /^\s/,

+

+    // selectors follow

+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,

+    id:           /^#([\w\-\*]+)(\b|$)/,

+    className:    /^\.([\w\-\*]+)(\b|$)/,

+    pseudo:

+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,

+    attrPresence: /^\[([\w]+)\]/,

+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/

+  },

+

+  // for Selector.match and Element#match

+  assertions: {

+    tagName: function(element, matches) {

+      return matches[1].toUpperCase() == element.tagName.toUpperCase();

+    },

+

+    className: function(element, matches) {

+      return Element.hasClassName(element, matches[1]);

+    },

+

+    id: function(element, matches) {

+      return element.id === matches[1];

+    },

+

+    attrPresence: function(element, matches) {

+      return Element.hasAttribute(element, matches[1]);

+    },

+

+    attr: function(element, matches) {

+      var nodeValue = Element.readAttribute(element, matches[1]);

+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);

+    }

+  },

+

+  handlers: {

+    // UTILITY FUNCTIONS

+    // joins two collections

+    concat: function(a, b) {

+      for (var i = 0, node; node = b[i]; i++)

+        a.push(node);

+      return a;

+    },

+

+    // marks an array of nodes for counting

+    mark: function(nodes) {

+      var _true = Prototype.emptyFunction;

+      for (var i = 0, node; node = nodes[i]; i++)

+        node._countedByPrototype = _true;

+      return nodes;

+    },

+

+    unmark: function(nodes) {

+      for (var i = 0, node; node = nodes[i]; i++)

+        node._countedByPrototype = undefined;

+      return nodes;

+    },

+

+    // mark each child node with its position (for nth calls)

+    // "ofType" flag indicates whether we're indexing for nth-of-type

+    // rather than nth-child

+    index: function(parentNode, reverse, ofType) {

+      parentNode._countedByPrototype = Prototype.emptyFunction;

+      if (reverse) {

+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {

+          var node = nodes[i];

+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;

+        }

+      } else {

+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)

+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;

+      }

+    },

+

+    // filters out duplicates and extends all nodes

+    unique: function(nodes) {

+      if (nodes.length == 0) return nodes;

+      var results = [], n;

+      for (var i = 0, l = nodes.length; i < l; i++)

+        if (!(n = nodes[i])._countedByPrototype) {

+          n._countedByPrototype = Prototype.emptyFunction;

+          results.push(Element.extend(n));

+        }

+      return Selector.handlers.unmark(results);

+    },

+

+    // COMBINATOR FUNCTIONS

+    descendant: function(nodes) {

+      var h = Selector.handlers;

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        h.concat(results, node.getElementsByTagName('*'));

+      return results;

+    },

+

+    child: function(nodes) {

+      var h = Selector.handlers;

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        for (var j = 0, child; child = node.childNodes[j]; j++)

+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);

+      }

+      return results;

+    },

+

+    adjacent: function(nodes) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        var next = this.nextElementSibling(node);

+        if (next) results.push(next);

+      }

+      return results;

+    },

+

+    laterSibling: function(nodes) {

+      var h = Selector.handlers;

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        h.concat(results, Element.nextSiblings(node));

+      return results;

+    },

+

+    nextElementSibling: function(node) {

+      while (node = node.nextSibling)

+	      if (node.nodeType == 1) return node;

+      return null;

+    },

+

+    previousElementSibling: function(node) {

+      while (node = node.previousSibling)

+        if (node.nodeType == 1) return node;

+      return null;

+    },

+

+    // TOKEN FUNCTIONS

+    tagName: function(nodes, root, tagName, combinator) {

+      var uTagName = tagName.toUpperCase();

+      var results = [], h = Selector.handlers;

+      if (nodes) {

+        if (combinator) {

+          // fastlane for ordinary descendant combinators

+          if (combinator == "descendant") {

+            for (var i = 0, node; node = nodes[i]; i++)

+              h.concat(results, node.getElementsByTagName(tagName));

+            return results;

+          } else nodes = this[combinator](nodes);

+          if (tagName == "*") return nodes;

+        }

+        for (var i = 0, node; node = nodes[i]; i++)

+          if (node.tagName.toUpperCase() === uTagName) results.push(node);

+        return results;

+      } else return root.getElementsByTagName(tagName);

+    },

+

+    id: function(nodes, root, id, combinator) {

+      var targetNode = $(id), h = Selector.handlers;

+      if (!targetNode) return [];

+      if (!nodes && root == document) return [targetNode];

+      if (nodes) {

+        if (combinator) {

+          if (combinator == 'child') {

+            for (var i = 0, node; node = nodes[i]; i++)

+              if (targetNode.parentNode == node) return [targetNode];

+          } else if (combinator == 'descendant') {

+            for (var i = 0, node; node = nodes[i]; i++)

+              if (Element.descendantOf(targetNode, node)) return [targetNode];

+          } else if (combinator == 'adjacent') {

+            for (var i = 0, node; node = nodes[i]; i++)

+              if (Selector.handlers.previousElementSibling(targetNode) == node)

+                return [targetNode];

+          } else nodes = h[combinator](nodes);

+        }

+        for (var i = 0, node; node = nodes[i]; i++)

+          if (node == targetNode) return [targetNode];

+        return [];

+      }

+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];

+    },

+

+    className: function(nodes, root, className, combinator) {

+      if (nodes && combinator) nodes = this[combinator](nodes);

+      return Selector.handlers.byClassName(nodes, root, className);

+    },

+

+    byClassName: function(nodes, root, className) {

+      if (!nodes) nodes = Selector.handlers.descendant([root]);

+      var needle = ' ' + className + ' ';

+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {

+        nodeClassName = node.className;

+        if (nodeClassName.length == 0) continue;

+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))

+          results.push(node);

+      }

+      return results;

+    },

+

+    attrPresence: function(nodes, root, attr, combinator) {

+      if (!nodes) nodes = root.getElementsByTagName("*");

+      if (nodes && combinator) nodes = this[combinator](nodes);

+      var results = [];

+      for (var i = 0, node; node = nodes[i]; i++)

+        if (Element.hasAttribute(node, attr)) results.push(node);

+      return results;

+    },

+

+    attr: function(nodes, root, attr, value, operator, combinator) {

+      if (!nodes) nodes = root.getElementsByTagName("*");

+      if (nodes && combinator) nodes = this[combinator](nodes);

+      var handler = Selector.operators[operator], results = [];

+      for (var i = 0, node; node = nodes[i]; i++) {

+        var nodeValue = Element.readAttribute(node, attr);

+        if (nodeValue === null) continue;

+        if (handler(nodeValue, value)) results.push(node);

+      }

+      return results;

+    },

+

+    pseudo: function(nodes, name, value, root, combinator) {

+      if (nodes && combinator) nodes = this[combinator](nodes);

+      if (!nodes) nodes = root.getElementsByTagName("*");

+      return Selector.pseudos[name](nodes, value, root);

+    }

+  },

+

+  pseudos: {

+    'first-child': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        if (Selector.handlers.previousElementSibling(node)) continue;

+          results.push(node);

+      }

+      return results;

+    },

+    'last-child': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        if (Selector.handlers.nextElementSibling(node)) continue;

+          results.push(node);

+      }

+      return results;

+    },

+    'only-child': function(nodes, value, root) {

+      var h = Selector.handlers;

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))

+          results.push(node);

+      return results;

+    },

+    'nth-child':        function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, formula, root);

+    },

+    'nth-last-child':   function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, formula, root, true);

+    },

+    'nth-of-type':      function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, formula, root, false, true);

+    },

+    'nth-last-of-type': function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, formula, root, true, true);

+    },

+    'first-of-type':    function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, "1", root, false, true);

+    },

+    'last-of-type':     function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, "1", root, true, true);

+    },

+    'only-of-type':     function(nodes, formula, root) {

+      var p = Selector.pseudos;

+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);

+    },

+

+    // handles the an+b logic

+    getIndices: function(a, b, total) {

+      if (a == 0) return b > 0 ? [b] : [];

+      return $R(1, total).inject([], function(memo, i) {

+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);

+        return memo;

+      });

+    },

+

+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type

+    nth: function(nodes, formula, root, reverse, ofType) {

+      if (nodes.length == 0) return [];

+      if (formula == 'even') formula = '2n+0';

+      if (formula == 'odd')  formula = '2n+1';

+      var h = Selector.handlers, results = [], indexed = [], m;

+      h.mark(nodes);

+      for (var i = 0, node; node = nodes[i]; i++) {

+        if (!node.parentNode._countedByPrototype) {

+          h.index(node.parentNode, reverse, ofType);

+          indexed.push(node.parentNode);

+        }

+      }

+      if (formula.match(/^\d+$/)) { // just a number

+        formula = Number(formula);

+        for (var i = 0, node; node = nodes[i]; i++)

+          if (node.nodeIndex == formula) results.push(node);

+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b

+        if (m[1] == "-") m[1] = -1;

+        var a = m[1] ? Number(m[1]) : 1;

+        var b = m[2] ? Number(m[2]) : 0;

+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);

+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {

+          for (var j = 0; j < l; j++)

+            if (node.nodeIndex == indices[j]) results.push(node);

+        }

+      }

+      h.unmark(nodes);

+      h.unmark(indexed);

+      return results;

+    },

+

+    'empty': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        // IE treats comments as element nodes

+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;

+        results.push(node);

+      }

+      return results;

+    },

+

+    'not': function(nodes, selector, root) {

+      var h = Selector.handlers, selectorType, m;

+      var exclusions = new Selector(selector).findElements(root);

+      h.mark(exclusions);

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (!node._countedByPrototype) results.push(node);

+      h.unmark(exclusions);

+      return results;

+    },

+

+    'enabled': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (!node.disabled) results.push(node);

+      return results;

+    },

+

+    'disabled': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (node.disabled) results.push(node);

+      return results;

+    },

+

+    'checked': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (node.checked) results.push(node);

+      return results;

+    }

+  },

+

+  operators: {

+    '=':  function(nv, v) { return nv == v; },

+    '!=': function(nv, v) { return nv != v; },

+    '^=': function(nv, v) { return nv.startsWith(v); },

+    '$=': function(nv, v) { return nv.endsWith(v); },

+    '*=': function(nv, v) { return nv.include(v); },

+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },

+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }

+  },

+

+  split: function(expression) {

+    var expressions = [];

+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {

+      expressions.push(m[1].strip());

+    });

+    return expressions;

+  },

+

+  matchElements: function(elements, expression) {

+    var matches = $$(expression), h = Selector.handlers;

+    h.mark(matches);

+    for (var i = 0, results = [], element; element = elements[i]; i++)

+      if (element._countedByPrototype) results.push(element);

+    h.unmark(matches);

+    return results;

+  },

+

+  findElement: function(elements, expression, index) {

+    if (Object.isNumber(expression)) {

+      index = expression; expression = false;

+    }

+    return Selector.matchElements(elements, expression || '*')[index || 0];

+  },

+

+  findChildElements: function(element, expressions) {

+    expressions = Selector.split(expressions.join(','));

+    var results = [], h = Selector.handlers;

+    for (var i = 0, l = expressions.length, selector; i < l; i++) {

+      selector = new Selector(expressions[i].strip());

+      h.concat(results, selector.findElements(element));

+    }

+    return (l > 1) ? h.unique(results) : results;

+  }

+});

+

+if (Prototype.Browser.IE) {

+  Object.extend(Selector.handlers, {

+    // IE returns comment nodes on getElementsByTagName("*").

+    // Filter them out.

+    concat: function(a, b) {

+      for (var i = 0, node; node = b[i]; i++)

+        if (node.tagName !== "!") a.push(node);

+      return a;

+    },

+

+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.

+    unmark: function(nodes) {

+      for (var i = 0, node; node = nodes[i]; i++)

+        node.removeAttribute('_countedByPrototype');

+      return nodes;

+    }

+  });

+}

+

+function $$() {

+  return Selector.findChildElements(document, $A(arguments));

+}

+var Form = {

+  reset: function(form) {

+    $(form).reset();

+    return form;

+  },

+

+  serializeElements: function(elements, options) {

+    if (typeof options != 'object') options = { hash: !!options };

+    else if (Object.isUndefined(options.hash)) options.hash = true;

+    var key, value, submitted = false, submit = options.submit;

+

+    var data = elements.inject({ }, function(result, element) {

+      if (!element.disabled && element.name) {

+        key = element.name; value = $(element).getValue();

+        if (value != null && (element.type != 'submit' || (!submitted &&

+            submit !== false && (!submit || key == submit) && (submitted = true)))) {

+          if (key in result) {

+            // a key is already present; construct an array of values

+            if (!Object.isArray(result[key])) result[key] = [result[key]];

+            result[key].push(value);

+          }

+          else result[key] = value;

+        }

+      }

+      return result;

+    });

+

+    return options.hash ? data : Object.toQueryString(data);

+  }

+};

+

+Form.Methods = {

+  serialize: function(form, options) {

+    return Form.serializeElements(Form.getElements(form), options);

+  },

+

+  getElements: function(form) {

+    return $A($(form).getElementsByTagName('*')).inject([],

+      function(elements, child) {

+        if (Form.Element.Serializers[child.tagName.toLowerCase()])

+          elements.push(Element.extend(child));

+        return elements;

+      }

+    );

+  },

+

+  getInputs: function(form, typeName, name) {

+    form = $(form);

+    var inputs = form.getElementsByTagName('input');

+

+    if (!typeName && !name) return $A(inputs).map(Element.extend);

+

+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {

+      var input = inputs[i];

+      if ((typeName && input.type != typeName) || (name && input.name != name))

+        continue;

+      matchingInputs.push(Element.extend(input));

+    }

+

+    return matchingInputs;

+  },

+

+  disable: function(form) {

+    form = $(form);

+    Form.getElements(form).invoke('disable');

+    return form;

+  },

+

+  enable: function(form) {

+    form = $(form);

+    Form.getElements(form).invoke('enable');

+    return form;

+  },

+

+  findFirstElement: function(form) {

+    var elements = $(form).getElements().findAll(function(element) {

+      return 'hidden' != element.type && !element.disabled;

+    });

+    var firstByIndex = elements.findAll(function(element) {

+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;

+    }).sortBy(function(element) { return element.tabIndex }).first();

+

+    return firstByIndex ? firstByIndex : elements.find(function(element) {

+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());

+    });

+  },

+

+  focusFirstElement: function(form) {

+    form = $(form);

+    form.findFirstElement().activate();

+    return form;

+  },

+

+  request: function(form, options) {

+    form = $(form), options = Object.clone(options || { });

+

+    var params = options.parameters, action = form.readAttribute('action') || '';

+    if (action.blank()) action = window.location.href;

+    options.parameters = form.serialize(true);

+

+    if (params) {

+      if (Object.isString(params)) params = params.toQueryParams();

+      Object.extend(options.parameters, params);

+    }

+

+    if (form.hasAttribute('method') && !options.method)

+      options.method = form.method;

+

+    return new Ajax.Request(action, options);

+  }

+};

+

+/*--------------------------------------------------------------------------*/

+

+Form.Element = {

+  focus: function(element) {

+    $(element).focus();

+    return element;

+  },

+

+  select: function(element) {

+    $(element).select();

+    return element;

+  }

+};

+

+Form.Element.Methods = {

+  serialize: function(element) {

+    element = $(element);

+    if (!element.disabled && element.name) {

+      var value = element.getValue();

+      if (value != undefined) {

+        var pair = { };

+        pair[element.name] = value;

+        return Object.toQueryString(pair);

+      }

+    }

+    return '';

+  },

+

+  getValue: function(element) {

+    element = $(element);

+    var method = element.tagName.toLowerCase();

+    return Form.Element.Serializers[method](element);

+  },

+

+  setValue: function(element, value) {

+    element = $(element);

+    var method = element.tagName.toLowerCase();

+    Form.Element.Serializers[method](element, value);

+    return element;

+  },

+

+  clear: function(element) {

+    $(element).value = '';

+    return element;

+  },

+

+  present: function(element) {

+    return $(element).value != '';

+  },

+

+  activate: function(element) {

+    element = $(element);

+    try {

+      element.focus();

+      if (element.select && (element.tagName.toLowerCase() != 'input' ||

+          !['button', 'reset', 'submit'].include(element.type)))

+        element.select();

+    } catch (e) { }

+    return element;

+  },

+

+  disable: function(element) {

+    element = $(element);

+    element.blur();

+    element.disabled = true;

+    return element;

+  },

+

+  enable: function(element) {

+    element = $(element);

+    element.disabled = false;

+    return element;

+  }

+};

+

+/*--------------------------------------------------------------------------*/

+

+var Field = Form.Element;

+var $F = Form.Element.Methods.getValue;

+

+/*--------------------------------------------------------------------------*/

+

+Form.Element.Serializers = {

+  input: function(element, value) {

+    switch (element.type.toLowerCase()) {

+      case 'checkbox':

+      case 'radio':

+        return Form.Element.Serializers.inputSelector(element, value);

+      default:

+        return Form.Element.Serializers.textarea(element, value);

+    }

+  },

+

+  inputSelector: function(element, value) {

+    if (Object.isUndefined(value)) return element.checked ? element.value : null;

+    else element.checked = !!value;

+  },

+

+  textarea: function(element, value) {

+    if (Object.isUndefined(value)) return element.value;

+    else element.value = value;

+  },

+

+  select: function(element, index) {

+    if (Object.isUndefined(index))

+      return this[element.type == 'select-one' ?

+        'selectOne' : 'selectMany'](element);

+    else {

+      var opt, value, single = !Object.isArray(index);

+      for (var i = 0, length = element.length; i < length; i++) {

+        opt = element.options[i];

+        value = this.optionValue(opt);

+        if (single) {

+          if (value == index) {

+            opt.selected = true;

+            return;

+          }

+        }

+        else opt.selected = index.include(value);

+      }

+    }

+  },

+

+  selectOne: function(element) {

+    var index = element.selectedIndex;

+    return index >= 0 ? this.optionValue(element.options[index]) : null;

+  },

+

+  selectMany: function(element) {

+    var values, length = element.length;

+    if (!length) return null;

+

+    for (var i = 0, values = []; i < length; i++) {

+      var opt = element.options[i];

+      if (opt.selected) values.push(this.optionValue(opt));

+    }

+    return values;

+  },

+

+  optionValue: function(opt) {

+    // extend element because hasAttribute may not be native

+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;

+  }

+};

+

+/*--------------------------------------------------------------------------*/

+

+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {

+  initialize: function($super, element, frequency, callback) {

+    $super(callback, frequency);

+    this.element   = $(element);

+    this.lastValue = this.getValue();

+  },

+

+  execute: function() {

+    var value = this.getValue();

+    if (Object.isString(this.lastValue) && Object.isString(value) ?

+        this.lastValue != value : String(this.lastValue) != String(value)) {

+      this.callback(this.element, value);

+      this.lastValue = value;

+    }

+  }

+});

+

+Form.Element.Observer = Class.create(Abstract.TimedObserver, {

+  getValue: function() {

+    return Form.Element.getValue(this.element);

+  }

+});

+

+Form.Observer = Class.create(Abstract.TimedObserver, {

+  getValue: function() {

+    return Form.serialize(this.element);

+  }

+});

+

+/*--------------------------------------------------------------------------*/

+

+Abstract.EventObserver = Class.create({

+  initialize: function(element, callback) {

+    this.element  = $(element);

+    this.callback = callback;

+

+    this.lastValue = this.getValue();

+    if (this.element.tagName.toLowerCase() == 'form')

+      this.registerFormCallbacks();

+    else

+      this.registerCallback(this.element);

+  },

+

+  onElementEvent: function() {

+    var value = this.getValue();

+    if (this.lastValue != value) {

+      this.callback(this.element, value);

+      this.lastValue = value;

+    }

+  },

+

+  registerFormCallbacks: function() {

+    Form.getElements(this.element).each(this.registerCallback, this);

+  },

+

+  registerCallback: function(element) {

+    if (element.type) {

+      switch (element.type.toLowerCase()) {

+        case 'checkbox':

+        case 'radio':

+          Event.observe(element, 'click', this.onElementEvent.bind(this));

+          break;

+        default:

+          Event.observe(element, 'change', this.onElementEvent.bind(this));

+          break;

+      }

+    }

+  }

+});

+

+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {

+  getValue: function() {

+    return Form.Element.getValue(this.element);

+  }

+});

+

+Form.EventObserver = Class.create(Abstract.EventObserver, {

+  getValue: function() {

+    return Form.serialize(this.element);

+  }

+});

+if (!window.Event) var Event = { };

+

+Object.extend(Event, {

+  KEY_BACKSPACE: 8,

+  KEY_TAB:       9,

+  KEY_RETURN:   13,

+  KEY_ESC:      27,

+  KEY_LEFT:     37,

+  KEY_UP:       38,

+  KEY_RIGHT:    39,

+  KEY_DOWN:     40,

+  KEY_DELETE:   46,

+  KEY_HOME:     36,

+  KEY_END:      35,

+  KEY_PAGEUP:   33,

+  KEY_PAGEDOWN: 34,

+  KEY_INSERT:   45,

+

+  cache: { },

+

+  relatedTarget: function(event) {

+    var element;

+    switch(event.type) {

+      case 'mouseover': element = event.fromElement; break;

+      case 'mouseout':  element = event.toElement;   break;

+      default: return null;

+    }

+    return Element.extend(element);

+  }

+});

+

+Event.Methods = (function() {

+  var isButton;

+

+  if (Prototype.Browser.IE) {

+    var buttonMap = { 0: 1, 1: 4, 2: 2 };

+    isButton = function(event, code) {

+      return event.button == buttonMap[code];

+    };

+

+  } else if (Prototype.Browser.WebKit) {

+    isButton = function(event, code) {

+      switch (code) {

+        case 0: return event.which == 1 && !event.metaKey;

+        case 1: return event.which == 1 && event.metaKey;

+        default: return false;

+      }

+    };

+

+  } else {

+    isButton = function(event, code) {

+      return event.which ? (event.which === code + 1) : (event.button === code);

+    };

+  }

+

+  return {

+    isLeftClick:   function(event) { return isButton(event, 0) },

+    isMiddleClick: function(event) { return isButton(event, 1) },

+    isRightClick:  function(event) { return isButton(event, 2) },

+

+    element: function(event) {

+      var node = Event.extend(event).target;

+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);

+    },

+

+    findElement: function(event, expression) {

+      var element = Event.element(event);

+      if (!expression) return element;

+      var elements = [element].concat(element.ancestors());

+      return Selector.findElement(elements, expression, 0);

+    },

+

+    pointer: function(event) {

+      return {

+        x: event.pageX || (event.clientX +

+          (document.documentElement.scrollLeft || document.body.scrollLeft)),

+        y: event.pageY || (event.clientY +

+          (document.documentElement.scrollTop || document.body.scrollTop))

+      };

+    },

+

+    pointerX: function(event) { return Event.pointer(event).x },

+    pointerY: function(event) { return Event.pointer(event).y },

+

+    stop: function(event) {

+      Event.extend(event);

+      event.preventDefault();

+      event.stopPropagation();

+      event.stopped = true;

+    }

+  };

+})();

+

+Event.extend = (function() {

+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {

+    m[name] = Event.Methods[name].methodize();

+    return m;

+  });

+

+  if (Prototype.Browser.IE) {

+    Object.extend(methods, {

+      stopPropagation: function() { this.cancelBubble = true },

+      preventDefault:  function() { this.returnValue = false },

+      inspect: function() { return "[object Event]" }

+    });

+

+    return function(event) {

+      if (!event) return false;

+      if (event._extendedByPrototype) return event;

+

+      event._extendedByPrototype = Prototype.emptyFunction;

+      var pointer = Event.pointer(event);

+      Object.extend(event, {

+        target: event.srcElement,

+        relatedTarget: Event.relatedTarget(event),

+        pageX:  pointer.x,

+        pageY:  pointer.y

+      });

+      return Object.extend(event, methods);

+    };

+

+  } else {

+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;

+    Object.extend(Event.prototype, methods);

+    return Prototype.K;

+  }

+})();

+

+Object.extend(Event, (function() {

+  var cache = Event.cache;

+

+  function getEventID(element) {

+    if (element._prototypeEventID) return element._prototypeEventID[0];

+    arguments.callee.id = arguments.callee.id || 1;

+    return element._prototypeEventID = [++arguments.callee.id];

+  }

+

+  function getDOMEventName(eventName) {

+    if (eventName && eventName.include(':')) return "dataavailable";

+    return eventName;

+  }

+

+  function getCacheForID(id) {

+    return cache[id] = cache[id] || { };

+  }

+

+  function getWrappersForEventName(id, eventName) {

+    var c = getCacheForID(id);

+    return c[eventName] = c[eventName] || [];

+  }

+

+  function createWrapper(element, eventName, handler) {

+    var id = getEventID(element);

+    var c = getWrappersForEventName(id, eventName);

+    if (c.pluck("handler").include(handler)) return false;

+

+    var wrapper = function(event) {

+      if (!Event || !Event.extend ||

+        (event.eventName && event.eventName != eventName))

+          return false;

+

+      Event.extend(event);

+      handler.call(element, event);

+    };

+

+    wrapper.handler = handler;

+    c.push(wrapper);

+    return wrapper;

+  }

+

+  function findWrapper(id, eventName, handler) {

+    var c = getWrappersForEventName(id, eventName);

+    return c.find(function(wrapper) { return wrapper.handler == handler });

+  }

+

+  function destroyWrapper(id, eventName, handler) {

+    var c = getCacheForID(id);

+    if (!c[eventName]) return false;

+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));

+  }

+

+  function destroyCache() {

+    for (var id in cache)

+      for (var eventName in cache[id])

+        cache[id][eventName] = null;

+  }

+

+  if (window.attachEvent) {

+    window.attachEvent("onunload", destroyCache);

+  }

+

+  return {

+    observe: function(element, eventName, handler) {

+      element = $(element);

+      var name = getDOMEventName(eventName);

+

+      var wrapper = createWrapper(element, eventName, handler);

+      if (!wrapper) return element;

+

+      if (element.addEventListener) {

+        element.addEventListener(name, wrapper, false);

+      } else {

+        element.attachEvent("on" + name, wrapper);

+      }

+

+      return element;

+    },

+

+    stopObserving: function(element, eventName, handler) {

+      element = $(element);

+      var id = getEventID(element), name = getDOMEventName(eventName);

+

+      if (!handler && eventName) {

+        getWrappersForEventName(id, eventName).each(function(wrapper) {

+          element.stopObserving(eventName, wrapper.handler);

+        });

+        return element;

+

+      } else if (!eventName) {

+        Object.keys(getCacheForID(id)).each(function(eventName) {

+          element.stopObserving(eventName);

+        });

+        return element;

+      }

+

+      var wrapper = findWrapper(id, eventName, handler);

+      if (!wrapper) return element;

+

+      if (element.removeEventListener) {

+        element.removeEventListener(name, wrapper, false);

+      } else {

+        element.detachEvent("on" + name, wrapper);

+      }

+

+      destroyWrapper(id, eventName, handler);

+

+      return element;

+    },

+

+    fire: function(element, eventName, memo) {

+      element = $(element);

+      if (element == document && document.createEvent && !element.dispatchEvent)

+        element = document.documentElement;

+

+      var event;

+      if (document.createEvent) {

+        event = document.createEvent("HTMLEvents");

+        event.initEvent("dataavailable", true, true);

+      } else {

+        event = document.createEventObject();

+        event.eventType = "ondataavailable";

+      }

+

+      event.eventName = eventName;

+      event.memo = memo || { };

+

+      if (document.createEvent) {

+        element.dispatchEvent(event);

+      } else {

+        element.fireEvent(event.eventType, event);

+      }

+

+      return Event.extend(event);

+    }

+  };

+})());

+

+Object.extend(Event, Event.Methods);

+

+Element.addMethods({

+  fire:          Event.fire,

+  observe:       Event.observe,

+  stopObserving: Event.stopObserving

+});

+

+Object.extend(document, {

+  fire:          Element.Methods.fire.methodize(),

+  observe:       Element.Methods.observe.methodize(),

+  stopObserving: Element.Methods.stopObserving.methodize(),

+  loaded:        false

+});

+

+(function() {

+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,

+     Matthias Miller, Dean Edwards and John Resig. */

+

+  var timer;

+

+  function fireContentLoadedEvent() {

+    if (document.loaded) return;

+    if (timer) window.clearInterval(timer);

+    document.fire("dom:loaded");

+    document.loaded = true;

+  }

+

+  if (document.addEventListener) {

+    if (Prototype.Browser.WebKit) {

+      timer = window.setInterval(function() {

+        if (/loaded|complete/.test(document.readyState))

+          fireContentLoadedEvent();

+      }, 0);

+

+      Event.observe(window, "load", fireContentLoadedEvent);

+

+    } else {

+      document.addEventListener("DOMContentLoaded",

+        fireContentLoadedEvent, false);

+    }

+

+  } else {

+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");

+    $("__onDOMContentLoaded").onreadystatechange = function() {

+      if (this.readyState == "complete") {

+        this.onreadystatechange = null;

+        fireContentLoadedEvent();

+      }

+    };

+  }

+})();

+/*------------------------------- DEPRECATED -------------------------------*/

+

+Hash.toQueryString = Object.toQueryString;

+

+var Toggle = { display: Element.toggle };

+

+Element.Methods.childOf = Element.Methods.descendantOf;

+

+var Insertion = {

+  Before: function(element, content) {

+    return Element.insert(element, {before:content});

+  },

+

+  Top: function(element, content) {

+    return Element.insert(element, {top:content});

+  },

+

+  Bottom: function(element, content) {

+    return Element.insert(element, {bottom:content});

+  },

+

+  After: function(element, content) {

+    return Element.insert(element, {after:content});

+  }

+};

+

+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

+

+// This should be moved to script.aculo.us; notice the deprecated methods

+// further below, that map to the newer Element methods.

+var Position = {

+  // set to true if needed, warning: firefox performance problems

+  // NOT neeeded for page scrolling, only if draggable contained in

+  // scrollable elements

+  includeScrollOffsets: false,

+

+  // must be called before calling withinIncludingScrolloffset, every time the

+  // page is scrolled

+  prepare: function() {

+    this.deltaX =  window.pageXOffset

+                || document.documentElement.scrollLeft

+                || document.body.scrollLeft

+                || 0;

+    this.deltaY =  window.pageYOffset

+                || document.documentElement.scrollTop

+                || document.body.scrollTop

+                || 0;

+  },

+

+  // caches x/y coordinate pair to use with overlap

+  within: function(element, x, y) {

+    if (this.includeScrollOffsets)

+      return this.withinIncludingScrolloffsets(element, x, y);

+    this.xcomp = x;

+    this.ycomp = y;

+    this.offset = Element.cumulativeOffset(element);

+

+    return (y >= this.offset[1] &&

+            y <  this.offset[1] + element.offsetHeight &&

+            x >= this.offset[0] &&

+            x <  this.offset[0] + element.offsetWidth);

+  },

+

+  withinIncludingScrolloffsets: function(element, x, y) {

+    var offsetcache = Element.cumulativeScrollOffset(element);

+

+    this.xcomp = x + offsetcache[0] - this.deltaX;

+    this.ycomp = y + offsetcache[1] - this.deltaY;

+    this.offset = Element.cumulativeOffset(element);

+

+    return (this.ycomp >= this.offset[1] &&

+            this.ycomp <  this.offset[1] + element.offsetHeight &&

+            this.xcomp >= this.offset[0] &&

+            this.xcomp <  this.offset[0] + element.offsetWidth);

+  },

+

+  // within must be called directly before

+  overlap: function(mode, element) {

+    if (!mode) return 0;

+    if (mode == 'vertical')

+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /

+        element.offsetHeight;

+    if (mode == 'horizontal')

+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /

+        element.offsetWidth;

+  },

+

+  // Deprecation layer -- use newer Element methods now (1.5.2).

+

+  cumulativeOffset: Element.Methods.cumulativeOffset,

+

+  positionedOffset: Element.Methods.positionedOffset,

+

+  absolutize: function(element) {

+    Position.prepare();

+    return Element.absolutize(element);

+  },

+

+  relativize: function(element) {

+    Position.prepare();

+    return Element.relativize(element);

+  },

+

+  realOffset: Element.Methods.cumulativeScrollOffset,

+

+  offsetParent: Element.Methods.getOffsetParent,

+

+  page: Element.Methods.viewportOffset,

+

+  clone: function(source, target, options) {

+    options = options || { };

+    return Element.clonePosition(target, source, options);

+  }

+};

+

+/*--------------------------------------------------------------------------*/

+

+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){

+  function iter(name) {

+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";

+  }

+

+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?

+  function(element, className) {

+    className = className.toString().strip();

+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);

+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];

+  } : function(element, className) {

+    className = className.toString().strip();

+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);

+    if (!classNames && !className) return elements;

+

+    var nodes = $(element).getElementsByTagName('*');

+    className = ' ' + className + ' ';

+

+    for (var i = 0, child, cn; child = nodes[i]; i++) {

+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||

+          (classNames && classNames.all(function(name) {

+            return !name.toString().blank() && cn.include(' ' + name + ' ');

+          }))))

+        elements.push(Element.extend(child));

+    }

+    return elements;

+  };

+

+  return function(className, parentElement) {

+    return $(parentElement || document.body).getElementsByClassName(className);

+  };

+}(Element.Methods);

+

+/*--------------------------------------------------------------------------*/

+

+Element.ClassNames = Class.create();

+Element.ClassNames.prototype = {

+  initialize: function(element) {

+    this.element = $(element);

+  },

+

+  _each: function(iterator) {

+    this.element.className.split(/\s+/).select(function(name) {

+      return name.length > 0;

+    })._each(iterator);

+  },

+

+  set: function(className) {

+    this.element.className = className;

+  },

+

+  add: function(classNameToAdd) {

+    if (this.include(classNameToAdd)) return;

+    this.set($A(this).concat(classNameToAdd).join(' '));

+  },

+

+  remove: function(classNameToRemove) {

+    if (!this.include(classNameToRemove)) return;

+    this.set($A(this).without(classNameToRemove).join(' '));

+  },

+

+  toString: function() {

+    return $A(this).join(' ');

+  }

+};

+

+Object.extend(Element.ClassNames.prototype, Enumerable);

+

+/*--------------------------------------------------------------------------*/

+

+Element.addMethods();

--- a/js/jquery-1.5.js
+++ b/js/jquery-1.5.js
@@ -1,5 +1,5 @@
 /*!
- * jQuery JavaScript Library v1.5
+ * jQuery JavaScript Library v1.5.2
  * http://jquery.com/
  *
  * Copyright 2011, John Resig
@@ -11,7 +11,7 @@
  * Copyright 2011, The Dojo Foundation
  * Released under the MIT, BSD, and GPL Licenses.
  *
- * Date: Mon Jan 31 08:31:29 2011 -0500
+ * Date: Thu Mar 31 15:28:23 2011 -0400
  */
 (function( window, undefined ) {
 
@@ -69,14 +69,8 @@
 	// For matching the engine and version of the browser
 	browserMatch,
 
-	// Has the ready events already been bound?
-	readyBound = false,
-
 	// The deferred used on DOM ready
 	readyList,
-
-	// Promise methods
-	promiseMethods = "then done fail isResolved isRejected promise".split( " " ),
 
 	// The ready event handler
 	DOMContentLoaded,
@@ -202,7 +196,7 @@
 	selector: "",
 
 	// The current version of jQuery being used
-	jquery: "1.5",
+	jquery: "1.5.2",
 
 	// The default length of a jQuery object is 0
 	length: 0,
@@ -313,7 +307,7 @@
 jQuery.fn.init.prototype = jQuery.fn;
 
 jQuery.extend = jQuery.fn.extend = function() {
-	 var options, name, src, copy, copyIsArray, clone,
+	var options, name, src, copy, copyIsArray, clone,
 		target = arguments[0] || {},
 		i = 1,
 		length = arguments.length,
@@ -427,11 +421,11 @@
 	},
 
 	bindReady: function() {
-		if ( readyBound ) {
+		if ( readyList ) {
 			return;
 		}
 
-		readyBound = true;
+		readyList = jQuery._Deferred();
 
 		// Catch cases where $(document).ready() is called after the
 		// browser event has already occurred.
@@ -585,10 +579,8 @@
 		if ( data && rnotwhite.test(data) ) {
 			// Inspired by code by Andrea Giammarchi
 			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
-			var head = document.getElementsByTagName("head")[0] || document.documentElement,
-				script = document.createElement("script");
-
-			script.type = "text/javascript";
+			var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement,
+				script = document.createElement( "script" );
 
 			if ( jQuery.support.scriptEval() ) {
 				script.appendChild( document.createTextNode( data ) );
@@ -813,6 +805,123 @@
 		return (new Date()).getTime();
 	},
 
+	// Use of jQuery.browser is frowned upon.
+	// More details: http://docs.jquery.com/Utilities/jQuery.browser
+	uaMatch: function( ua ) {
+		ua = ua.toLowerCase();
+
+		var match = rwebkit.exec( ua ) ||
+			ropera.exec( ua ) ||
+			rmsie.exec( ua ) ||
+			ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+			[];
+
+		return { browser: match[1] || "", version: match[2] || "0" };
+	},
+
+	sub: function() {
+		function jQuerySubclass( selector, context ) {
+			return new jQuerySubclass.fn.init( selector, context );
+		}
+		jQuery.extend( true, jQuerySubclass, this );
+		jQuerySubclass.superclass = this;
+		jQuerySubclass.fn = jQuerySubclass.prototype = this();
+		jQuerySubclass.fn.constructor = jQuerySubclass;
+		jQuerySubclass.subclass = this.subclass;
+		jQuerySubclass.fn.init = function init( selector, context ) {
+			if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) {
+				context = jQuerySubclass(context);
+			}
+
+			return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass );
+		};
+		jQuerySubclass.fn.init.prototype = jQuerySubclass.fn;
+		var rootjQuerySubclass = jQuerySubclass(document);
+		return jQuerySubclass;
+	},
+
+	browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+	jQuery.browser[ browserMatch.browser ] = true;
+	jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+	jQuery.browser.safari = true;
+}
+
+if ( indexOf ) {
+	jQuery.inArray = function( elem, array ) {
+		return indexOf.call( array, elem );
+	};
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+	trimLeft = /^[\s\xA0]+/;
+	trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+	DOMContentLoaded = function() {
+		document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+		jQuery.ready();
+	};
+
+} else if ( document.attachEvent ) {
+	DOMContentLoaded = function() {
+		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+		if ( document.readyState === "complete" ) {
+			document.detachEvent( "onreadystatechange", DOMContentLoaded );
+			jQuery.ready();
+		}
+	};
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+	if ( jQuery.isReady ) {
+		return;
+	}
+
+	try {
+		// If IE is used, use the trick by Diego Perini
+		// http://javascript.nwbox.com/IEContentLoaded/
+		document.documentElement.doScroll("left");
+	} catch(e) {
+		setTimeout( doScrollCheck, 1 );
+		return;
+	}
+
+	// and execute any waiting functions
+	jQuery.ready();
+}
+
+// Expose jQuery to the global object
+return jQuery;
+
+})();
+
+
+var // Promise methods
+	promiseMethods = "then done fail isResolved isRejected promise".split( " " ),
+	// Static reference to slice
+	sliceDeferred = [].slice;
+
+jQuery.extend({
 	// Create a simple deferred (one callbacks list)
 	_Deferred: function() {
 		var // callbacks list
@@ -858,6 +967,8 @@
 				// resolve with given context and args
 				resolveWith: function( context, args ) {
 					if ( !cancelled && !fired && !firing ) {
+						// make sure args are available (#8421)
+						args = args || [];
 						firing = 1;
 						try {
 							while( callbacks[ 0 ] ) {
@@ -874,7 +985,7 @@
 
 				// resolve with this as context and given arguments
 				resolve: function() {
-					deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments );
+					deferred.resolveWith( this, arguments );
 					return this;
 				},
 
@@ -911,22 +1022,22 @@
 			isRejected: failDeferred.isResolved,
 			// Get a promise for this deferred
 			// If obj is provided, the promise aspect is added to the object
-			promise: function( obj , i /* internal */ ) {
+			promise: function( obj ) {
 				if ( obj == null ) {
 					if ( promise ) {
 						return promise;
 					}
 					promise = obj = {};
 				}
-				i = promiseMethods.length;
+				var i = promiseMethods.length;
 				while( i-- ) {
-					obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ];
+					obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
 				}
 				return obj;
 			}
 		} );
 		// Make sure only one callback list will be used
-		deferred.then( failDeferred.cancel, deferred.cancel );
+		deferred.done( failDeferred.cancel ).fail( deferred.cancel );
 		// Unexpose cancel
 		delete deferred.cancel;
 		// Call given func if any
@@ -937,143 +1048,44 @@
 	},
 
 	// Deferred helper
-	when: function( object ) {
+	when: function( firstParam ) {
 		var args = arguments,
+			i = 0,
 			length = args.length,
-			deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ?
-				object :
-				jQuery.Deferred(),
-			promise = deferred.promise(),
-			resolveArray;
-
+			count = length,
+			deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+				firstParam :
+				jQuery.Deferred();
+		function resolveFunc( i ) {
+			return function( value ) {
+				args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+				if ( !( --count ) ) {
+					// Strange bug in FF4:
+					// Values changed onto the arguments object sometimes end up as undefined values
+					// outside the $.when method. Cloning the object into a fresh array solves the issue
+					deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
+				}
+			};
+		}
 		if ( length > 1 ) {
-			resolveArray = new Array( length );
-			jQuery.each( args, function( index, element ) {
-				jQuery.when( element ).then( function( value ) {
-					resolveArray[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
-					if( ! --length ) {
-						deferred.resolveWith( promise, resolveArray );
-					}
-				}, deferred.reject );
-			} );
-		} else if ( deferred !== object ) {
-			deferred.resolve( object );
-		}
-		return promise;
-	},
-
-	// Use of jQuery.browser is frowned upon.
-	// More details: http://docs.jquery.com/Utilities/jQuery.browser
-	uaMatch: function( ua ) {
-		ua = ua.toLowerCase();
-
-		var match = rwebkit.exec( ua ) ||
-			ropera.exec( ua ) ||
-			rmsie.exec( ua ) ||
-			ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
-			[];
-
-		return { browser: match[1] || "", version: match[2] || "0" };
-	},
-
-	sub: function() {
-		function jQuerySubclass( selector, context ) {
-			return new jQuerySubclass.fn.init( selector, context );
-		}
-		jQuery.extend( true, jQuerySubclass, this );
-		jQuerySubclass.superclass = this;
-		jQuerySubclass.fn = jQuerySubclass.prototype = this();
-		jQuerySubclass.fn.constructor = jQuerySubclass;
-		jQuerySubclass.subclass = this.subclass;
-		jQuerySubclass.fn.init = function init( selector, context ) {
-			if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) {
-				context = jQuerySubclass(context);
-			}
-
-			return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass );
-		};
-		jQuerySubclass.fn.init.prototype = jQuerySubclass.fn;
-		var rootjQuerySubclass = jQuerySubclass(document);
-		return jQuerySubclass;
-	},
-
-	browser: {}
+			for( ; i < length; i++ ) {
+				if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
+					args[ i ].promise().then( resolveFunc(i), deferred.reject );
+				} else {
+					--count;
+				}
+			}
+			if ( !count ) {
+				deferred.resolveWith( deferred, args );
+			}
+		} else if ( deferred !== firstParam ) {
+			deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+		}
+		return deferred.promise();
+	}
 });
 
-// Create readyList deferred
-readyList = jQuery._Deferred();
-
-// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
-	class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
-
-browserMatch = jQuery.uaMatch( userAgent );
-if ( browserMatch.browser ) {
-	jQuery.browser[ browserMatch.browser ] = true;
-	jQuery.browser.version = browserMatch.version;
-}
-
-// Deprecated, use jQuery.browser.webkit instead
-if ( jQuery.browser.webkit ) {
-	jQuery.browser.safari = true;
-}
-
-if ( indexOf ) {
-	jQuery.inArray = function( elem, array ) {
-		return indexOf.call( array, elem );
-	};
-}
-
-// IE doesn't match non-breaking spaces with \s
-if ( rnotwhite.test( "\xA0" ) ) {
-	trimLeft = /^[\s\xA0]+/;
-	trimRight = /[\s\xA0]+$/;
-}
-
-// All jQuery objects should point back to these
-rootjQuery = jQuery(document);
-
-// Cleanup functions for the document ready method
-if ( document.addEventListener ) {
-	DOMContentLoaded = function() {
-		document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-		jQuery.ready();
-	};
-
-} else if ( document.attachEvent ) {
-	DOMContentLoaded = function() {
-		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
-		if ( document.readyState === "complete" ) {
-			document.detachEvent( "onreadystatechange", DOMContentLoaded );
-			jQuery.ready();
-		}
-	};
-}
-
-// The DOM ready check for Internet Explorer
-function doScrollCheck() {
-	if ( jQuery.isReady ) {
-		return;
-	}
-
-	try {
-		// If IE is used, use the trick by Diego Perini
-		// http://javascript.nwbox.com/IEContentLoaded/
-		document.documentElement.doScroll("left");
-	} catch(e) {
-		setTimeout( doScrollCheck, 1 );
-		return;
-	}
-
-	// and execute any waiting functions
-	jQuery.ready();
-}
-
-// Expose jQuery to the global object
-return (window.jQuery = window.$ = jQuery);
-
-})();
+
 
 
 (function() {
@@ -1088,7 +1100,8 @@
 	var all = div.getElementsByTagName("*"),
 		a = div.getElementsByTagName("a")[0],
 		select = document.createElement("select"),
-		opt = select.appendChild( document.createElement("option") );
+		opt = select.appendChild( document.createElement("option") ),
+		input = div.getElementsByTagName("input")[0];
 
 	// Can't get basic test support
 	if ( !all || !all.length || !a ) {
@@ -1127,7 +1140,7 @@
 		// Make sure that if no value is specified for a checkbox
 		// that it defaults to "on".
 		// (WebKit defaults to "" instead)
-		checkOn: div.getElementsByTagName("input")[0].value === "on",
+		checkOn: input.value === "on",
 
 		// Make sure that a selected-by-default option has a working selected property.
 		// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
@@ -1137,48 +1150,50 @@
 		deleteExpando: true,
 		optDisabled: false,
 		checkClone: false,
-		_scriptEval: null,
 		noCloneEvent: true,
+		noCloneChecked: true,
 		boxModel: null,
 		inlineBlockNeedsLayout: false,
 		shrinkWrapBlocks: false,
-		reliableHiddenOffsets: true
+		reliableHiddenOffsets: true,
+		reliableMarginRight: true
 	};
+
+	input.checked = true;
+	jQuery.support.noCloneChecked = input.cloneNode( true ).checked;
 
 	// Make sure that the options inside disabled selects aren't marked as disabled
 	// (WebKit marks them as diabled)
 	select.disabled = true;
 	jQuery.support.optDisabled = !opt.disabled;
 
+	var _scriptEval = null;
 	jQuery.support.scriptEval = function() {
-		if ( jQuery.support._scriptEval === null ) {
+		if ( _scriptEval === null ) {
 			var root = document.documentElement,
 				script = document.createElement("script"),
 				id = "script" + jQuery.now();
 
-			script.type = "text/javascript";
+			// Make sure that the execution of code works by injecting a script
+			// tag with appendChild/createTextNode
+			// (IE doesn't support this, fails, and uses .text instead)
 			try {
 				script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
 			} catch(e) {}
 
 			root.insertBefore( script, root.firstChild );
 
-			// Make sure that the execution of code works by injecting a script
-			// tag with appendChild/createTextNode
-			// (IE doesn't support this, fails, and uses .text instead)
 			if ( window[ id ] ) {
-				jQuery.support._scriptEval = true;
+				_scriptEval = true;
 				delete window[ id ];
 			} else {
-				jQuery.support._scriptEval = false;
+				_scriptEval = false;
 			}
 
 			root.removeChild( script );
-			// release memory in IE
-			root = script = id  = null;
-		}
-
-		return jQuery.support._scriptEval;
+		}
+
+		return _scriptEval;
 	};
 
 	// Test to see if it's possible to delete an expando from an element
@@ -1190,7 +1205,7 @@
 		jQuery.support.deleteExpando = false;
 	}
 
-	if ( div.attachEvent && div.fireEvent ) {
+	if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
 		div.attachEvent("onclick", function click() {
 			// Cloning a node shouldn't copy over any
 			// bound event handlers (IE does this)
@@ -1260,6 +1275,17 @@
 		jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0;
 		div.innerHTML = "";
 
+		// Check if div with explicit width and no margin-right incorrectly
+		// gets computed margin-right based on width of container. For more
+		// info see bug #3333
+		// Fails in WebKit before Feb 2011 nightlies
+		// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+		if ( document.defaultView && document.defaultView.getComputedStyle ) {
+			div.style.width = "1px";
+			div.style.marginRight = "0";
+			jQuery.support.reliableMarginRight = ( parseInt(document.defaultView.getComputedStyle(div, null).marginRight, 10) || 0 ) === 0;
+		}
+
 		body.removeChild( div ).style.display = "none";
 		div = tds = null;
 	});
@@ -1283,8 +1309,6 @@
 			el.setAttribute(eventName, "return;");
 			isSupported = typeof el[eventName] === "function";
 		}
-		el = null;
-
 		return isSupported;
 	};
 
@@ -1321,7 +1345,7 @@
 	hasData: function( elem ) {
 		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
 
-		return !!elem && !jQuery.isEmptyObject(elem);
+		return !!elem && !isEmptyDataObject( elem );
 	},
 
 	data: function( elem, name, data, pvt /* Internal Use Only */ ) {
@@ -1361,11 +1385,18 @@
 
 		if ( !cache[ id ] ) {
 			cache[ id ] = {};
+
+			// TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+			// metadata on plain JS objects when the object is serialized using
+			// JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
 		}
 
 		// An object can be passed to jQuery.data instead of a key/value pair; this gets
 		// shallow copied over onto the existing cache
-		if ( typeof name === "object" ) {
+		if ( typeof name === "object" || typeof name === "function" ) {
 			if ( pvt ) {
 				cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
 			} else {
@@ -1427,7 +1458,7 @@
 
 				// If there is no data left in the cache, we want to continue
 				// and let the cache object itself get destroyed
-				if ( !jQuery.isEmptyObject(thisCache) ) {
+				if ( !isEmptyDataObject(thisCache) ) {
 					return;
 				}
 			}
@@ -1439,7 +1470,7 @@
 
 			// Don't destroy the parent cache unless the internal data object
 			// had been the only thing left in it
-			if ( !jQuery.isEmptyObject(cache[ id ]) ) {
+			if ( !isEmptyDataObject(cache[ id ]) ) {
 				return;
 			}
 		}
@@ -1460,6 +1491,13 @@
 		// data if it existed
 		if ( internalCache ) {
 			cache[ id ] = {};
+			// TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+			// metadata on plain JS objects when the object is serialized using
+			// JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
+
 			cache[ id ][ internalKey ] = internalCache;
 
 		// Otherwise, we need to eliminate the expando on the node to avoid
@@ -1588,6 +1626,19 @@
 	return data;
 }
 
+// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON
+// property to be considered empty objects; this property always exists in
+// order to make sure JSON.stringify does not expose internal metadata
+function isEmptyDataObject( obj ) {
+	for ( var name in obj ) {
+		if ( name !== "toJSON" ) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
 
 
 
@@ -1888,6 +1939,11 @@
 						}
 					}
 
+					// Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+					if ( one && !values.length && options.length ) {
+						return jQuery( options[ index ] ).val();
+					}
+
 					return values;
 				}
 
@@ -2081,8 +2137,7 @@
 	rescape = /[^\w\s.|`]/g,
 	fcleanup = function( nm ) {
 		return nm.replace(rescape, "\\$&");
-	},
-	eventKey = "events";
+	};
 
 /*
  * A number of helper functions used for managing events.
@@ -2098,17 +2153,22 @@
 			return;
 		}
 
-		// For whatever reason, IE has trouble passing the window object
-		// around, causing it to be cloned in the process
-		if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
-			elem = window;
-		}
+		// TODO :: Use a try/catch until it's safe to pull this out (likely 1.6)
+		// Minor release fix for bug #8018
+		try {
+			// For whatever reason, IE has trouble passing the window object
+			// around, causing it to be cloned in the process
+			if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
+				elem = window;
+			}
+		}
+		catch ( e ) {}
 
 		if ( handler === false ) {
 			handler = returnFalse;
 		} else if ( !handler ) {
 			// Fixes bug #7229. Fix recommended by jdalton
-		  return;
+			return;
 		}
 
 		var handleObjIn, handleObj;
@@ -2132,31 +2192,18 @@
 			return;
 		}
 
-		var events = elemData[ eventKey ],
+		var events = elemData.events,
 			eventHandle = elemData.handle;
 
-		if ( typeof events === "function" ) {
-			// On plain objects events is a fn that holds the the data
-			// which prevents this data from being JSON serialized
-			// the function does not need to be called, it just contains the data
-			eventHandle = events.handle;
-			events = events.events;
-
-		} else if ( !events ) {
-			if ( !elem.nodeType ) {
-				// On plain objects, create a fn that acts as the holder
-				// of the values to avoid JSON serialization of event data
-				elemData[ eventKey ] = elemData = function(){};
-			}
-
+		if ( !events ) {
 			elemData.events = events = {};
 		}
 
 		if ( !eventHandle ) {
-			elemData.handle = eventHandle = function() {
+			elemData.handle = eventHandle = function( e ) {
 				// Handle the second event of a trigger and when
 				// an event is called after a page has unloaded
-				return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
 					jQuery.event.handle.apply( eventHandle.elem, arguments ) :
 					undefined;
 			};
@@ -2249,15 +2296,10 @@
 
 		var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
 			elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
-			events = elemData && elemData[ eventKey ];
+			events = elemData && elemData.events;
 
 		if ( !elemData || !events ) {
 			return;
-		}
-
-		if ( typeof events === "function" ) {
-			elemData = events;
-			events = events.events;
 		}
 
 		// types is actually an event object here
@@ -2359,10 +2401,7 @@
 			delete elemData.events;
 			delete elemData.handle;
 
-			if ( typeof elemData === "function" ) {
-				jQuery.removeData( elem, eventKey, true );
-
-			} else if ( jQuery.isEmptyObject( elemData ) ) {
+			if ( jQuery.isEmptyObject( elemData ) ) {
 				jQuery.removeData( elem, undefined, true );
 			}
 		}
@@ -2403,7 +2442,7 @@
 						// points to jQuery.expando
 						var internalKey = jQuery.expando,
 							internalCache = this[ internalKey ];
-						if ( internalCache && internalCache.events && internalCache.events[type] ) {
+						if ( internalCache && internalCache.events && internalCache.events[ type ] ) {
 							jQuery.event.trigger( event, data, internalCache.handle.elem );
 						}
 					});
@@ -2429,9 +2468,7 @@
 		event.currentTarget = elem;
 
 		// Trigger the event, it is assumed that "handle" is a function
-		var handle = elem.nodeType ?
-			jQuery._data( elem, "handle" ) :
-			(jQuery._data( elem, eventKey ) || {}).handle;
+		var handle = jQuery._data( elem, "handle" );
 
 		if ( handle ) {
 			handle.apply( elem, data );
@@ -2473,7 +2510,7 @@
 							target[ "on" + targetType ] = null;
 						}
 
-						jQuery.event.triggered = true;
+						jQuery.event.triggered = event.type;
 						target[ targetType ]();
 					}
 
@@ -2484,7 +2521,7 @@
 					target[ "on" + targetType ] = old;
 				}
 
-				jQuery.event.triggered = false;
+				jQuery.event.triggered = undefined;
 			}
 		}
 	},
@@ -2509,11 +2546,7 @@
 
 		event.namespace = event.namespace || namespace_sort.join(".");
 
-		events = jQuery._data(this, eventKey);
-
-		if ( typeof events === "function" ) {
-			events = events.events;
-		}
+		events = jQuery._data(this, "events");
 
 		handlers = (events || {})[ event.type ];
 
@@ -2680,7 +2713,7 @@
 
 		// Events bubbling up the document may have been marked as prevented
 		// by a handler lower down the tree; reflect the correct value.
-		this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || 
+		this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
 			src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
 
 	// Event type
@@ -2755,6 +2788,12 @@
 	// Firefox sometimes assigns relatedTarget a XUL element
 	// which we cannot access the parentNode property of
 	try {
+
+		// Chrome does something similar, the parentNode property
+		// can be accessed but is null.
+		if ( parent && parent !== document && !parent.parentNode ) {
+			return;
+		}
 		// Traverse up the tree
 		while ( parent && parent !== this ) {
 			parent = parent.parentNode;
@@ -2805,8 +2844,7 @@
 						type = elem.type;
 
 					if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
-						e.liveFired = undefined;
-						return trigger( "submit", this, arguments );
+						trigger( "submit", this, arguments );
 					}
 				});
 
@@ -2815,8 +2853,7 @@
 						type = elem.type;
 
 					if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
-						e.liveFired = undefined;
-						return trigger( "submit", this, arguments );
+						trigger( "submit", this, arguments );
 					}
 				});
 
@@ -2879,7 +2916,7 @@
 		if ( data != null || val ) {
 			e.type = "change";
 			e.liveFired = undefined;
-			return jQuery.event.trigger( e, arguments[1], elem );
+			jQuery.event.trigger( e, arguments[1], elem );
 		}
 	};
 
@@ -2893,7 +2930,7 @@
 				var elem = e.target, type = elem.type;
 
 				if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
-					return testChange.call( this, e );
+					testChange.call( this, e );
 				}
 			},
 
@@ -2905,7 +2942,7 @@
 				if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
 					(e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
 					type === "select-multiple" ) {
-					return testChange.call( this, e );
+					testChange.call( this, e );
 				}
 			},
 
@@ -2944,26 +2981,50 @@
 }
 
 function trigger( type, elem, args ) {
-	args[0].type = type;
-	return jQuery.event.handle.apply( elem, args );
+	// Piggyback on a donor event to simulate a different one.
+	// Fake originalEvent to avoid donor's stopPropagation, but if the
+	// simulated event prevents default then we do the same on the donor.
+	// Don't pass args or remember liveFired; they apply to the donor event.
+	var event = jQuery.extend( {}, args[ 0 ] );
+	event.type = type;
+	event.originalEvent = {};
+	event.liveFired = undefined;
+	jQuery.event.handle.call( elem, event );
+	if ( event.isDefaultPrevented() ) {
+		args[ 0 ].preventDefault();
+	}
 }
 
 // Create "bubbling" focus and blur events
 if ( document.addEventListener ) {
 	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+	
+		// Attach a single capturing handler while someone wants focusin/focusout
+		var attaches = 0;
+		
 		jQuery.event.special[ fix ] = {
 			setup: function() {
-				this.addEventListener( orig, handler, true );
-			}, 
-			teardown: function() { 
-				this.removeEventListener( orig, handler, true );
+				if ( attaches++ === 0 ) {
+					document.addEventListener( orig, handler, true );
+				}
+			},
+			teardown: function() {
+				if ( --attaches === 0 ) {
+					document.removeEventListener( orig, handler, true );
+				}
 			}
 		};
 
-		function handler( e ) {
-			e = jQuery.event.fix( e );
+		function handler( donor ) {
+			// Donor event is always a native one; fix it and switch its type.
+			// Let focusin/out handler cancel the donor focus/blur event.
+			var e = jQuery.event.fix( donor );
 			e.type = fix;
-			return jQuery.event.handle.call( this, e );
+			e.originalEvent = {};
+			jQuery.event.trigger( e, null, e.target );
+			if ( e.isDefaultPrevented() ) {
+				donor.preventDefault();
+			}
 		}
 	});
 }
@@ -3148,11 +3209,7 @@
 	var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
 		elems = [],
 		selectors = [],
-		events = jQuery._data( this, eventKey );
-
-	if ( typeof events === "function" ) {
-		events = events.events;
-	}
+		events = jQuery._data( this, "events" );
 
 	// Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
 	if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
@@ -3186,7 +3243,7 @@
 		for ( j = 0; j < live.length; j++ ) {
 			handleObj = live[j];
 
-			if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) {
+			if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
 				elem = close.elem;
 				related = null;
 
@@ -3269,7 +3326,9 @@
 	done = 0,
 	toString = Object.prototype.toString,
 	hasDuplicate = false,
-	baseHasDuplicate = true;
+	baseHasDuplicate = true,
+	rBackslash = /\\/g,
+	rNonWord = /\W/;
 
 // Here we check if the JavaScript engine is using some sort of
 // optimization where it does not always call our comparision
@@ -3468,7 +3527,7 @@
 			match.splice( 1, 1 );
 
 			if ( left.substr( left.length - 1 ) !== "\\" ) {
-				match[1] = (match[1] || "").replace(/\\/g, "");
+				match[1] = (match[1] || "").replace( rBackslash, "" );
 				set = Expr.find[ type ]( match, context, isXML );
 
 				if ( set != null ) {
@@ -3607,13 +3666,16 @@
 	attrHandle: {
 		href: function( elem ) {
 			return elem.getAttribute( "href" );
+		},
+		type: function( elem ) {
+			return elem.getAttribute( "type" );
 		}
 	},
 
 	relative: {
 		"+": function(checkSet, part){
 			var isPartStr = typeof part === "string",
-				isTag = isPartStr && !/\W/.test( part ),
+				isTag = isPartStr && !rNonWord.test( part ),
 				isPartStrNotTag = isPartStr && !isTag;
 
 			if ( isTag ) {
@@ -3641,7 +3703,7 @@
 				i = 0,
 				l = checkSet.length;
 
-			if ( isPartStr && !/\W/.test( part ) ) {
+			if ( isPartStr && !rNonWord.test( part ) ) {
 				part = part.toLowerCase();
 
 				for ( ; i < l; i++ ) {
@@ -3675,7 +3737,7 @@
 				doneName = done++,
 				checkFn = dirCheck;
 
-			if ( typeof part === "string" && !/\W/.test(part) ) {
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
 				part = part.toLowerCase();
 				nodeCheck = part;
 				checkFn = dirNodeCheck;
@@ -3689,7 +3751,7 @@
 				doneName = done++,
 				checkFn = dirCheck;
 
-			if ( typeof part === "string" && !/\W/.test( part ) ) {
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
 				part = part.toLowerCase();
 				nodeCheck = part;
 				checkFn = dirNodeCheck;
@@ -3732,7 +3794,7 @@
 	},
 	preFilter: {
 		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
-			match = " " + match[1].replace(/\\/g, "") + " ";
+			match = " " + match[1].replace( rBackslash, "" ) + " ";
 
 			if ( isXML ) {
 				return match;
@@ -3755,11 +3817,11 @@
 		},
 
 		ID: function( match ) {
-			return match[1].replace(/\\/g, "");
+			return match[1].replace( rBackslash, "" );
 		},
 
 		TAG: function( match, curLoop ) {
-			return match[1].toLowerCase();
+			return match[1].replace( rBackslash, "" ).toLowerCase();
 		},
 
 		CHILD: function( match ) {
@@ -3790,14 +3852,14 @@
 		},
 
 		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
-			var name = match[1] = match[1].replace(/\\/g, "");
+			var name = match[1] = match[1].replace( rBackslash, "" );
 			
 			if ( !isXML && Expr.attrMap[name] ) {
 				match[1] = Expr.attrMap[name];
 			}
 
 			// Handle if an un-quoted value was used
-			match[4] = ( match[4] || match[5] || "" ).replace(/\\/g, "");
+			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
 
 			if ( match[2] === "~=" ) {
 				match[4] = " " + match[4] + " ";
@@ -3852,7 +3914,9 @@
 		selected: function( elem ) {
 			// Accessing this property makes selected-by-default
 			// options in Safari work properly
-			elem.parentNode.selectedIndex;
+			if ( elem.parentNode ) {
+				elem.parentNode.selectedIndex;
+			}
 			
 			return elem.selected === true;
 		},
@@ -3874,8 +3938,12 @@
 		},
 
 		text: function( elem ) {
-			return "text" === elem.type;
+			var attr = elem.getAttribute( "type" ), type = elem.type;
+			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 
+			// use getAttribute instead to test this case
+			return "text" === type && ( attr === type || attr === null );
 		},
+
 		radio: function( elem ) {
 			return "radio" === elem.type;
 		},
@@ -4407,7 +4475,8 @@
 				// and working up from there (Thanks to Andrew Dupont for the technique)
 				// IE 8 doesn't work on object elements
 				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
-					var old = context.getAttribute( "id" ),
+					var oldContext = context,
+						old = context.getAttribute( "id" ),
 						nid = old || id,
 						hasParent = context.parentNode,
 						relativeHierarchySelector = /^\s*[+~]/.test( query );
@@ -4429,7 +4498,7 @@
 					} catch(pseudoError) {
 					} finally {
 						if ( !old ) {
-							context.removeAttribute( "id" );
+							oldContext.removeAttribute( "id" );
 						}
 					}
 				}
@@ -4449,19 +4518,23 @@
 
 (function(){
 	var html = document.documentElement,
-		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector,
-		pseudoWorks = false;
-
-	try {
-		// This should fail with an exception
-		// Gecko does not error, returns false instead
-		matches.call( document.documentElement, "[test!='']:sizzle" );
+		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+	if ( matches ) {
+		// Check to see if it's possible to do matchesSelector
+		// on a disconnected node (IE 9 fails this)
+		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+			pseudoWorks = false;
+
+		try {
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( document.documentElement, "[test!='']:sizzle" );
 	
-	} catch( pseudoError ) {
-		pseudoWorks = true;
-	}
-
-	if ( matches ) {
+		} catch( pseudoError ) {
+			pseudoWorks = true;
+		}
+
 		Sizzle.matchesSelector = function( node, expr ) {
 			// Make sure that attribute selectors are quoted
 			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
@@ -4469,7 +4542,15 @@
 			if ( !Sizzle.isXML( node ) ) {
 				try { 
 					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
-						return matches.call( node, expr );
+						var ret = matches.call( node, expr );
+
+						// IE 9's matchesSelector returns false on disconnected nodes
+						if ( ret || !disconnectedMatch ||
+								// As well, disconnected nodes are said to be in a document
+								// fragment in IE 9, so check for that
+								node.document && node.document.nodeType !== 11 ) {
+							return ret;
+						}
 					}
 				} catch(e) {}
 			}
@@ -4845,11 +4926,11 @@
 }, function( name, fn ) {
 	jQuery.fn[ name ] = function( until, selector ) {
 		var ret = jQuery.map( this, fn, until ),
-                // The variable 'args' was introduced in
-                // https://github.com/jquery/jquery/commit/52a0238
-                // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
-                // http://code.google.com/p/v8/issues/detail?id=1050
-                    args = slice.call(arguments);
+			// The variable 'args' was introduced in
+			// https://github.com/jquery/jquery/commit/52a0238
+			// to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
+			// http://code.google.com/p/v8/issues/detail?id=1050
+			args = slice.call(arguments);
 
 		if ( !runtil.test( name ) ) {
 			selector = until;
@@ -4959,7 +5040,7 @@
 	rtbody = /<tbody/i,
 	rhtml = /<|&#?\w+;/,
 	rnocache = /<(?:script|object|embed|option|style)/i,
-	// checked="checked" or checked (html5)
+	// checked="checked" or checked
 	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
 	wrapMap = {
 		option: [ 1, "<select multiple='multiple'>", "</select>" ],
@@ -5111,7 +5192,7 @@
 				}
 
 				if ( elem.parentNode ) {
-					 elem.parentNode.removeChild( elem );
+					elem.parentNode.removeChild( elem );
 				}
 			}
 		}
@@ -5136,7 +5217,7 @@
 	},
 
 	clone: function( dataAndEvents, deepDataAndEvents ) {
-		dataAndEvents = dataAndEvents == null ? true : dataAndEvents;
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
 		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
 
 		return this.map( function () {
@@ -5213,7 +5294,9 @@
 				}
 			});
 		} else {
-			return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value );
+			return this.length ?
+				this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+				this;
 		}
 	},
 
@@ -5305,8 +5388,8 @@
 	}
 
 	var internalKey = jQuery.expando,
-			oldData = jQuery.data( src ),
-			curData = jQuery.data( dest, oldData );
+		oldData = jQuery.data( src ),
+		curData = jQuery.data( dest, oldData );
 
 	// Switch to use the internal data object, if it exists, for the next
 	// stage of data copying
@@ -5320,7 +5403,7 @@
 
 			for ( var type in events ) {
 				for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
-					jQuery.event.add( dest, type, events[ type ][ i ], events[ type ][ i ].data );
+					jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
 				}
 			}
 		}
@@ -5441,6 +5524,18 @@
 	};
 });
 
+function getAll( elem ) {
+	if ( "getElementsByTagName" in elem ) {
+		return elem.getElementsByTagName( "*" );
+	
+	} else if ( "querySelectorAll" in elem ) {
+		return elem.querySelectorAll( "*" );
+
+	} else {
+		return [];
+	}
+}
+
 jQuery.extend({
 	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
 		var clone = elem.cloneNode(true),
@@ -5448,17 +5543,20 @@
 				destElements,
 				i;
 
-		if ( !jQuery.support.noCloneEvent && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
 			// IE copies events bound via attachEvent when using cloneNode.
 			// Calling detachEvent on the clone will also remove the events
 			// from the original. In order to get around this, we use some
 			// proprietary methods to clear the events. Thanks to MooTools
 			// guys for this hotness.
 
+			cloneFixAttributes( elem, clone );
+
 			// Using Sizzle here is crazy slow, so we use getElementsByTagName
 			// instead
-			srcElements = elem.getElementsByTagName("*");
-			destElements = clone.getElementsByTagName("*");
+			srcElements = getAll( elem );
+			destElements = getAll( clone );
 
 			// Weird iteration because IE will replace the length property
 			// with an element if you are cloning the body and one of the
@@ -5466,30 +5564,25 @@
 			for ( i = 0; srcElements[i]; ++i ) {
 				cloneFixAttributes( srcElements[i], destElements[i] );
 			}
-
-			cloneFixAttributes( elem, clone );
 		}
 
 		// Copy the events from the original to the clone
 		if ( dataAndEvents ) {
-
 			cloneCopyEvent( elem, clone );
 
-			if ( deepDataAndEvents && "getElementsByTagName" in elem ) {
-
-				srcElements = elem.getElementsByTagName("*");
-				destElements = clone.getElementsByTagName("*");
-
-				if ( srcElements.length ) {
-					for ( i = 0; srcElements[i]; ++i ) {
-						cloneCopyEvent( srcElements[i], destElements[i] );
-					}
-				}
-			}
-		}
+			if ( deepDataAndEvents ) {
+				srcElements = getAll( elem );
+				destElements = getAll( clone );
+
+				for ( i = 0; srcElements[i]; ++i ) {
+					cloneCopyEvent( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
 		// Return the cloned set
 		return clone;
-  },
+},
 	clean: function( elems, context, fragment, scripts ) {
 		context = context || document;
 
@@ -5650,7 +5743,8 @@
 var ralpha = /alpha\([^)]*\)/i,
 	ropacity = /opacity=([^)]*)/,
 	rdashAlpha = /-([a-z])/ig,
-	rupper = /([A-Z])/g,
+	// fixed for IE9, see #8346
+	rupper = /([A-Z]|^ms)/g,
 	rnumpx = /^-?\d+(?:px)?$/i,
 	rnum = /^-?\d/,
 
@@ -5887,6 +5981,28 @@
 	};
 }
 
+jQuery(function() {
+	// This hook cannot be added until DOM ready because the support test
+	// for it is not run until after DOM ready
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// Work around by temporarily setting element display to inline-block
+				var ret;
+				jQuery.swap( elem, { "display": "inline-block" }, function() {
+					if ( computed ) {
+						ret = curCSS( elem, "margin-right", "marginRight" );
+					} else {
+						ret = elem.style.marginRight;
+					}
+				});
+				return ret;
+			}
+		};
+	}
+});
+
 if ( document.defaultView && document.defaultView.getComputedStyle ) {
 	getComputedStyle = function( elem, newName, name ) {
 		var ret, defaultView, computedStyle;
@@ -5910,7 +6026,7 @@
 
 if ( document.documentElement.currentStyle ) {
 	currentStyle = function( elem, name ) {
-		var left, 
+		var left,
 			ret = elem.currentStyle && elem.currentStyle[ name ],
 			rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ],
 			style = elem.style;
@@ -5988,8 +6104,10 @@
 	rbracket = /\[\]$/,
 	rCRLF = /\r?\n/g,
 	rhash = /#.*$/,
-	rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
 	rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|widget):$/,
 	rnoContent = /^(?:GET|HEAD)$/,
 	rprotocol = /^\/\//,
 	rquery = /\?/,
@@ -5997,7 +6115,11 @@
 	rselectTextarea = /^(?:select|textarea)/i,
 	rspacesAjax = /\s+/,
 	rts = /([?&])_=[^&]*/,
-	rurl = /^(\w+:)\/\/([^\/?#:]+)(?::(\d+))?/,
+	rucHeaders = /(^|\-)([a-z])/g,
+	rucHeadersFunc = function( _, $1, $2 ) {
+		return $1 + $2.toUpperCase();
+	},
+	rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
 
 	// Keep a copy of the old load method
 	_load = jQuery.fn.load,
@@ -6018,7 +6140,28 @@
 	 * 2) the catchall symbol "*" can be used
 	 * 3) selection will start with transport dataType and THEN go to "*" if needed
 	 */
-	transports = {};
+	transports = {},
+
+	// Document location
+	ajaxLocation,
+
+	// Document location segments
+	ajaxLocParts;
+
+// #8138, IE may throw an exception when accessing
+// a field from document.location if document.domain has been set
+try {
+	ajaxLocation = document.location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
 
 // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
 function addToPrefiltersOrTransports( structure ) {
@@ -6057,7 +6200,7 @@
 }
 
 //Base inspection function for prefilters and transports
-function inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR,
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
 		dataType /* internal */, inspected /* internal */ ) {
 
 	dataType = dataType || options.dataTypes[ 0 ];
@@ -6072,16 +6215,16 @@
 		selection;
 
 	for(; i < length && ( executeOnly || !selection ); i++ ) {
-		selection = list[ i ]( options, originalOptions, jXHR );
+		selection = list[ i ]( options, originalOptions, jqXHR );
 		// If we got redirected to another dataType
-		// we try there if not done already
+		// we try there if executing only and not done already
 		if ( typeof selection === "string" ) {
-			if ( inspected[ selection ] ) {
+			if ( !executeOnly || inspected[ selection ] ) {
 				selection = undefined;
 			} else {
 				options.dataTypes.unshift( selection );
 				selection = inspectPrefiltersOrTransports(
-						structure, options, originalOptions, jXHR, selection, inspected );
+						structure, options, originalOptions, jqXHR, selection, inspected );
 			}
 		}
 	}
@@ -6089,7 +6232,7 @@
 	// we try the catchall dataType if not done already
 	if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
 		selection = inspectPrefiltersOrTransports(
-				structure, options, originalOptions, jXHR, "*", inspected );
+				structure, options, originalOptions, jqXHR, "*", inspected );
 	}
 	// unnecessary when only executing (prefilters)
 	// but it'll be ignored by the caller in that case
@@ -6121,7 +6264,7 @@
 			if ( jQuery.isFunction( params ) ) {
 				// We assume that it's the callback
 				callback = params;
-				params = null;
+				params = undefined;
 
 			// Otherwise, build a param string
 			} else if ( typeof params === "object" ) {
@@ -6139,14 +6282,14 @@
 			dataType: "html",
 			data: params,
 			// Complete callback (responseText is used internally)
-			complete: function( jXHR, status, responseText ) {
-				// Store the response as specified by the jXHR object
-				responseText = jXHR.responseText;
+			complete: function( jqXHR, status, responseText ) {
+				// Store the response as specified by the jqXHR object
+				responseText = jqXHR.responseText;
 				// If successful, inject the HTML into all the matched elements
-				if ( jXHR.isResolved() ) {
+				if ( jqXHR.isResolved() ) {
 					// #4825: Get the actual response in case
 					// a dataFilter is present in ajaxSettings
-					jXHR.done(function( r ) {
+					jqXHR.done(function( r ) {
 						responseText = r;
 					});
 					// See if a selector was specified
@@ -6165,7 +6308,7 @@
 				}
 
 				if ( callback ) {
-					self.each( callback, [ responseText, status, jXHR ] );
+					self.each( callback, [ responseText, status, jqXHR ] );
 				}
 			}
 		});
@@ -6213,7 +6356,7 @@
 		if ( jQuery.isFunction( data ) ) {
 			type = type || callback;
 			callback = data;
-			data = null;
+			data = undefined;
 		}
 
 		return jQuery.ajax({
@@ -6229,22 +6372,39 @@
 jQuery.extend({
 
 	getScript: function( url, callback ) {
-		return jQuery.get( url, null, callback, "script" );
+		return jQuery.get( url, undefined, callback, "script" );
 	},
 
 	getJSON: function( url, data, callback ) {
 		return jQuery.get( url, data, callback, "json" );
 	},
 
-	ajaxSetup: function( settings ) {
-		jQuery.extend( true, jQuery.ajaxSettings, settings );
-		if ( settings.context ) {
-			jQuery.ajaxSettings.context = settings.context;
-		}
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function ( target, settings ) {
+		if ( !settings ) {
+			// Only one parameter, we extend ajaxSettings
+			settings = target;
+			target = jQuery.extend( true, jQuery.ajaxSettings, settings );
+		} else {
+			// target was provided, we extend into it
+			jQuery.extend( true, target, jQuery.ajaxSettings, settings );
+		}
+		// Flatten fields we don't want deep extended
+		for( var field in { context: 1, url: 1 } ) {
+			if ( field in settings ) {
+				target[ field ] = settings[ field ];
+			} else if( field in jQuery.ajaxSettings ) {
+				target[ field ] = jQuery.ajaxSettings[ field ];
+			}
+		}
+		return target;
 	},
 
 	ajaxSettings: {
-		url: location.href,
+		url: ajaxLocation,
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
 		global: true,
 		type: "GET",
 		contentType: "application/x-www-form-urlencoded",
@@ -6259,7 +6419,6 @@
 		cache: null,
 		traditional: false,
 		headers: {},
-		crossDomain: null,
 		*/
 
 		accepts: {
@@ -6306,9 +6465,8 @@
 	// Main method
 	ajax: function( url, options ) {
 
-		// If options is not an object,
-		// we simulate pre-1.5 signature
-		if ( typeof options !== "object" ) {
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
 			options = url;
 			url = undefined;
 		}
@@ -6317,19 +6475,22 @@
 		options = options || {};
 
 		var // Create the final options object
-			s = jQuery.extend( true, {}, jQuery.ajaxSettings, options ),
-			// Callbacks contexts
-			// We force the original context if it exists
-			// or take it from jQuery.ajaxSettings otherwise
-			// (plain objects used as context get extended)
-			callbackContext =
-				( s.context = ( "context" in options ? options : jQuery.ajaxSettings ).context ) || s,
-			globalEventContext = callbackContext === s ? jQuery.event : jQuery( callbackContext ),
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events
+			// It's the callbackContext if one was provided in the options
+			// and if it's a DOM node or a jQuery collection
+			globalEventContext = callbackContext !== s &&
+				( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+						jQuery( callbackContext ) : jQuery.event,
 			// Deferreds
 			deferred = jQuery.Deferred(),
 			completeDeferred = jQuery._Deferred(),
 			// Status-dependent callbacks
 			statusCode = s.statusCode || {},
+			// ifModified key
+			ifModifiedKey,
 			// Headers (they are sent all at once)
 			requestHeaders = {},
 			// Response headers
@@ -6340,22 +6501,22 @@
 			// timeout handle
 			timeoutTimer,
 			// Cross-domain detection vars
-			loc = document.location,
-			protocol = loc.protocol || "http:",
 			parts,
-			// The jXHR state
+			// The jqXHR state
 			state = 0,
+			// To know if global events are to be dispatched
+			fireGlobals,
 			// Loop variable
 			i,
 			// Fake xhr
-			jXHR = {
+			jqXHR = {
 
 				readyState: 0,
 
 				// Caches the header
 				setRequestHeader: function( name, value ) {
-					if ( state === 0 ) {
-						requestHeaders[ name.toLowerCase() ] = value;
+					if ( !state ) {
+						requestHeaders[ name.toLowerCase().replace( rucHeaders, rucHeadersFunc ) ] = value;
 					}
 					return this;
 				},
@@ -6377,7 +6538,15 @@
 						}
 						match = responseHeaders[ key.toLowerCase() ];
 					}
-					return match || null;
+					return match === undefined ? null : match;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
 				},
 
 				// Cancel the request
@@ -6394,7 +6563,7 @@
 		// Callback for when everything is done
 		// It is defined here because jslint complains if it is declared
 		// at the end of the function (which would be more logical and readable)
-		function done( status, statusText, responses, headers) {
+		function done( status, statusText, responses, headers ) {
 
 			// Called once
 			if ( state === 2 ) {
@@ -6410,19 +6579,19 @@
 			}
 
 			// Dereference transport for early garbage collection
-			// (no matter how long the jXHR object will be used)
+			// (no matter how long the jqXHR object will be used)
 			transport = undefined;
 
 			// Cache response headers
 			responseHeadersString = headers || "";
 
 			// Set readyState
-			jXHR.readyState = status ? 4 : 0;
+			jqXHR.readyState = status ? 4 : 0;
 
 			var isSuccess,
 				success,
 				error,
-				response = responses ? ajaxHandleResponses( s, jXHR, responses ) : undefined,
+				response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
 				lastModified,
 				etag;
 
@@ -6432,11 +6601,11 @@
 				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
 				if ( s.ifModified ) {
 
-					if ( ( lastModified = jXHR.getResponseHeader( "Last-Modified" ) ) ) {
-						jQuery.lastModified[ s.url ] = lastModified;
-					}
-					if ( ( etag = jXHR.getResponseHeader( "Etag" ) ) ) {
-						jQuery.etag[ s.url ] = etag;
+					if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+						jQuery.lastModified[ ifModifiedKey ] = lastModified;
+					}
+					if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+						jQuery.etag[ ifModifiedKey ] = etag;
 					}
 				}
 
@@ -6463,7 +6632,7 @@
 				// We extract error from statusText
 				// then normalize statusText and status for non-aborts
 				error = statusText;
-				if( status ) {
+				if( !statusText || status ) {
 					statusText = "error";
 					if ( status < 0 ) {
 						status = 0;
@@ -6472,30 +6641,30 @@
 			}
 
 			// Set data for the fake xhr object
-			jXHR.status = status;
-			jXHR.statusText = statusText;
+			jqXHR.status = status;
+			jqXHR.statusText = statusText;
 
 			// Success/Error
 			if ( isSuccess ) {
-				deferred.resolveWith( callbackContext, [ success, statusText, jXHR ] );
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
 			} else {
-				deferred.rejectWith( callbackContext, [ jXHR, statusText, error ] );
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
 			}
 
 			// Status-dependent callbacks
-			jXHR.statusCode( statusCode );
+			jqXHR.statusCode( statusCode );
 			statusCode = undefined;
 
-			if ( s.global ) {
+			if ( fireGlobals ) {
 				globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
-						[ jXHR, s, isSuccess ? success : error ] );
+						[ jqXHR, s, isSuccess ? success : error ] );
 			}
 
 			// Complete
-			completeDeferred.resolveWith( callbackContext, [ jXHR, statusText ] );
-
-			if ( s.global ) {
-				globalEventContext.trigger( "ajaxComplete", [ jXHR, s] );
+			completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s] );
 				// Handle the global AJAX counter
 				if ( !( --jQuery.active ) ) {
 					jQuery.event.trigger( "ajaxStop" );
@@ -6504,13 +6673,13 @@
 		}
 
 		// Attach deferreds
-		deferred.promise( jXHR );
-		jXHR.success = jXHR.done;
-		jXHR.error = jXHR.fail;
-		jXHR.complete = completeDeferred.done;
+		deferred.promise( jqXHR );
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+		jqXHR.complete = completeDeferred.done;
 
 		// Status-dependent callbacks
-		jXHR.statusCode = function( map ) {
+		jqXHR.statusCode = function( map ) {
 			if ( map ) {
 				var tmp;
 				if ( state < 2 ) {
@@ -6518,8 +6687,8 @@
 						statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
 					}
 				} else {
-					tmp = map[ jXHR.status ];
-					jXHR.then( tmp, tmp );
+					tmp = map[ jqXHR.status ];
+					jqXHR.then( tmp, tmp );
 				}
 			}
 			return this;
@@ -6528,18 +6697,18 @@
 		// Remove hash character (#7531: and string promotion)
 		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
 		// We also use the url parameter if available
-		s.url = ( "" + ( url || s.url ) ).replace( rhash, "" ).replace( rprotocol, protocol + "//" );
+		s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
 
 		// Extract dataTypes list
 		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
 
 		// Determine if a cross-domain request is in order
-		if ( !s.crossDomain ) {
+		if ( s.crossDomain == null ) {
 			parts = rurl.exec( s.url.toLowerCase() );
 			s.crossDomain = !!( parts &&
-				( parts[ 1 ] != protocol || parts[ 2 ] != loc.hostname ||
+				( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
 					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
-						( loc.port || ( protocol === "http:" ? 80 : 443 ) ) )
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
 			);
 		}
 
@@ -6549,7 +6718,15 @@
 		}
 
 		// Apply prefilters
-		inspectPrefiltersOrTransports( prefilters, s, options, jXHR );
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefiler, stop there
+		if ( state === 2 ) {
+			return false;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
 
 		// Uppercase the type
 		s.type = s.type.toUpperCase();
@@ -6558,7 +6735,7 @@
 		s.hasContent = !rnoContent.test( s.type );
 
 		// Watch for a new set of requests
-		if ( s.global && jQuery.active++ === 0 ) {
+		if ( fireGlobals && jQuery.active++ === 0 ) {
 			jQuery.event.trigger( "ajaxStart" );
 		}
 
@@ -6570,6 +6747,9 @@
 				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
 			}
 
+			// Get ifModifiedKey before adding the anti-cache parameter
+			ifModifiedKey = s.url;
+
 			// Add anti-cache in url if needed
 			if ( s.cache === false ) {
 
@@ -6584,77 +6764,77 @@
 
 		// Set the correct header, if data is being sent
 		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
-			requestHeaders[ "content-type" ] = s.contentType;
+			requestHeaders[ "Content-Type" ] = s.contentType;
 		}
 
 		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
 		if ( s.ifModified ) {
-			if ( jQuery.lastModified[ s.url ] ) {
-				requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ s.url ];
-			}
-			if ( jQuery.etag[ s.url ] ) {
-				requestHeaders[ "if-none-match" ] = jQuery.etag[ s.url ];
+			ifModifiedKey = ifModifiedKey || s.url;
+			if ( jQuery.lastModified[ ifModifiedKey ] ) {
+				requestHeaders[ "If-Modified-Since" ] = jQuery.lastModified[ ifModifiedKey ];
+			}
+			if ( jQuery.etag[ ifModifiedKey ] ) {
+				requestHeaders[ "If-None-Match" ] = jQuery.etag[ ifModifiedKey ];
 			}
 		}
 
 		// Set the Accepts header for the server, depending on the dataType
-		requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+		requestHeaders.Accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
 			s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
 			s.accepts[ "*" ];
 
 		// Check for headers option
 		for ( i in s.headers ) {
-			requestHeaders[ i.toLowerCase() ] = s.headers[ i ];
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
 		}
 
 		// Allow custom headers/mimetypes and early abort
-		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jXHR, s ) === false || state === 2 ) ) {
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
 				// Abort if not done already
-				done( 0, "abort" );
-				// Return false
-				jXHR = false;
-
+				jqXHR.abort();
+				return false;
+
+		}
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
 		} else {
-
-			// Install callbacks on deferreds
-			for ( i in { success: 1, error: 1, complete: 1 } ) {
-				jXHR[ i ]( s[ i ] );
-			}
-
-			// Get transport
-			transport = inspectPrefiltersOrTransports( transports, s, options, jXHR );
-
-			// If no transport, we auto-abort
-			if ( !transport ) {
-				done( -1, "No Transport" );
-			} else {
-				// Set state as sending
-				state = jXHR.readyState = 1;
-				// Send global event
-				if ( s.global ) {
-					globalEventContext.trigger( "ajaxSend", [ jXHR, s ] );
-				}
-				// Timeout
-				if ( s.async && s.timeout > 0 ) {
-					timeoutTimer = setTimeout( function(){
-						jXHR.abort( "timeout" );
-					}, s.timeout );
-				}
-
-				try {
-					transport.send( requestHeaders, done );
-				} catch (e) {
-					// Propagate exception as error if not done
-					if ( status < 2 ) {
-						done( -1, e );
-					// Simply rethrow otherwise
-					} else {
-						jQuery.error( e );
-					}
-				}
-			}
-		}
-		return jXHR;
+			jqXHR.readyState = 1;
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout( function(){
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch (e) {
+				// Propagate exception as error if not done
+				if ( status < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					jQuery.error( e );
+				}
+			}
+		}
+
+		return jqXHR;
 	},
 
 	// Serialize an array of form elements or a set of
@@ -6673,7 +6853,7 @@
 		}
 
 		// If an array was passed in, assume that it is an array of form elements.
-		if ( jQuery.isArray( a ) || a.jquery ) {
+		if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
 			// Serialize the form elements
 			jQuery.each( a, function() {
 				add( this.name, this.value );
@@ -6720,9 +6900,9 @@
 
 		// Serialize object item.
 		} else {
-			jQuery.each( obj, function( k, v ) {
-				buildParams( prefix + "[" + k + "]", v, traditional, add );
-			});
+			for ( var name in obj ) {
+				buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+			}
 		}
 
 	} else {
@@ -6749,7 +6929,7 @@
  * - finds the right dataType (mediates between content-type and expected dataType)
  * - returns the corresponding response
  */
-function ajaxHandleResponses( s, jXHR, responses ) {
+function ajaxHandleResponses( s, jqXHR, responses ) {
 
 	var contents = s.contents,
 		dataTypes = s.dataTypes,
@@ -6762,7 +6942,7 @@
 	// Fill responseXXX fields
 	for( type in responseFields ) {
 		if ( type in responses ) {
-			jXHR[ responseFields[type] ] = responses[ type ];
+			jqXHR[ responseFields[type] ] = responses[ type ];
 		}
 	}
 
@@ -6770,7 +6950,7 @@
 	while( dataTypes[ 0 ] === "*" ) {
 		dataTypes.shift();
 		if ( ct === undefined ) {
-			ct = jXHR.getResponseHeader( "content-type" );
+			ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
 		}
 	}
 
@@ -6822,8 +7002,9 @@
 	}
 
 	var dataTypes = s.dataTypes,
-		converters = s.converters,
+		converters = {},
 		i,
+		key,
 		length = dataTypes.length,
 		tmp,
 		// Current and previous dataTypes
@@ -6839,6 +7020,16 @@
 
 	// For each dataType in the chain
 	for( i = 1; i < length; i++ ) {
+
+		// Create converters map
+		// with lowercased keys
+		if ( i === 1 ) {
+			for( key in s.converters ) {
+				if( typeof key === "string" ) {
+					converters[ key.toLowerCase() ] = s.converters[ key ];
+				}
+			}
+		}
 
 		// Get the dataTypes
 		prev = current;
@@ -6891,7 +7082,7 @@
 
 
 var jsc = jQuery.now(),
-	jsre = /(\=)\?(&|$)|()\?\?()/i;
+	jsre = /(\=)\?(&|$)|\?\?/i;
 
 // Default jsonp settings
 jQuery.ajaxSetup({
@@ -6902,9 +7093,9 @@
 });
 
 // Detect, normalize options and install callbacks for jsonp requests
-jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, dataIsString /* internal */ ) {
-
-	dataIsString = ( typeof s.data === "string" );
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var dataIsString = ( typeof s.data === "string" );
 
 	if ( s.dataTypes[ 0 ] === "jsonp" ||
 		originalSettings.jsonpCallback ||
@@ -6918,7 +7109,15 @@
 			previous = window[ jsonpCallback ],
 			url = s.url,
 			data = s.data,
-			replace = "$1" + jsonpCallback + "$2";
+			replace = "$1" + jsonpCallback + "$2",
+			cleanUp = function() {
+				// Set callback back to previous value
+				window[ jsonpCallback ] = previous;
+				// Call if it was a function and we have a response
+				if ( responseContainer && jQuery.isFunction( previous ) ) {
+					window[ jsonpCallback ]( responseContainer[ 0 ] );
+				}
+			};
 
 		if ( s.jsonp !== false ) {
 			url = url.replace( jsre, replace );
@@ -6936,32 +7135,17 @@
 		s.url = url;
 		s.data = data;
 
+		// Install callback
 		window[ jsonpCallback ] = function( response ) {
 			responseContainer = [ response ];
 		};
 
-		s.complete = [ function() {
-
-			// Set callback back to previous value
-			window[ jsonpCallback ] = previous;
-
-			// Call if it was a function and we have a response
-			if ( previous) {
-				if ( responseContainer && jQuery.isFunction( previous ) ) {
-					window[ jsonpCallback ] ( responseContainer[ 0 ] );
-				}
-			} else {
-				// else, more memory leak avoidance
-				try{
-					delete window[ jsonpCallback ];
-				} catch( e ) {}
-			}
-
-		}, s.complete ];
+		// Install cleanUp function
+		jqXHR.then( cleanUp, cleanUp );
 
 		// Use data converter to retrieve json after script execution
 		s.converters["script json"] = function() {
-			if ( ! responseContainer ) {
+			if ( !responseContainer ) {
 				jQuery.error( jsonpCallback + " was not called" );
 			}
 			return responseContainer[ 0 ];
@@ -6981,10 +7165,10 @@
 // Install script dataType
 jQuery.ajaxSetup({
 	accepts: {
-		script: "text/javascript, application/javascript"
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
 	},
 	contents: {
-		script: /javascript/
+		script: /javascript|ecmascript/
 	},
 	converters: {
 		"text script": function( text ) {
@@ -7012,7 +7196,7 @@
 	if ( s.crossDomain ) {
 
 		var script,
-			head = document.getElementsByTagName( "head" )[ 0 ] || document.documentElement;
+			head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
 
 		return {
 
@@ -7067,17 +7251,35 @@
 
 
 
-var // Next active xhr id
+var // #5280: next active xhr id and list of active xhrs' callbacks
 	xhrId = jQuery.now(),
-
-	// active xhrs
-	xhrs = {},
-
-	// #5280: see below
-	xhrUnloadAbortInstalled,
+	xhrCallbacks,
 
 	// XHR used to determine supports properties
 	testXHR;
+
+// #5280: Internet Explorer will keep connections alive if we don't abort on unload
+function xhrOnUnloadAbort() {
+	jQuery( window ).unload(function() {
+		// Abort all pending requests
+		for ( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]( 0, 1 );
+		}
+	});
+}
+
+// Functions to create xhrs
+function createStandardXHR() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch( e ) {}
+}
+
+function createActiveXHR() {
+	try {
+		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+	} catch( e ) {}
+}
 
 // Create the request object
 // (This is still attached to ajaxSettings for backward compatibility)
@@ -7089,27 +7291,13 @@
 	 * we need a fallback.
 	 */
 	function() {
-		if ( window.location.protocol !== "file:" ) {
-			try {
-				return new window.XMLHttpRequest();
-			} catch( xhrError ) {}
-		}
-
-		try {
-			return new window.ActiveXObject("Microsoft.XMLHTTP");
-		} catch( activeError ) {}
+		return !this.isLocal && createStandardXHR() || createActiveXHR();
 	} :
 	// For all other browsers, use the standard XMLHttpRequest object
-	function() {
-		return new window.XMLHttpRequest();
-	};
+	createStandardXHR;
 
 // Test if we can create an xhr object
-try {
-	testXHR = jQuery.ajaxSettings.xhr();
-} catch( xhrCreationException ) {}
-
-//Does this browser support XHR requests?
+testXHR = jQuery.ajaxSettings.xhr();
 jQuery.support.ajax = !!testXHR;
 
 // Does this browser support crossDomain XHR requests
@@ -7130,26 +7318,10 @@
 			return {
 				send: function( headers, complete ) {
 
-					// #5280: we need to abort on unload or IE will keep connections alive
-					if ( !xhrUnloadAbortInstalled ) {
-
-						xhrUnloadAbortInstalled = 1;
-
-						jQuery(window).bind( "unload", function() {
-
-							// Abort all pending requests
-							jQuery.each( xhrs, function( _, xhr ) {
-								if ( xhr.onreadystatechange ) {
-									xhr.onreadystatechange( 1 );
-								}
-							} );
-
-						} );
-					}
-
 					// Get a new xhr
 					var xhr = s.xhr(),
-						handle;
+						handle,
+						i;
 
 					// Open the socket
 					// Passing null username, generates a login popup on Opera (#2865)
@@ -7159,19 +7331,32 @@
 						xhr.open( s.type, s.url, s.async );
 					}
 
-					// Requested-With header
-					// Not set for crossDomain requests with no content
-					// (see why at http://trac.dojotoolkit.org/ticket/9486)
-					// Won't change header if already provided
-					if ( !( s.crossDomain && !s.hasContent ) && !headers["x-requested-with"] ) {
-						headers[ "x-requested-with" ] = "XMLHttpRequest";
+					// Apply custom fields if provided
+					if ( s.xhrFields ) {
+						for ( i in s.xhrFields ) {
+							xhr[ i ] = s.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( s.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( s.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
 					}
 
 					// Need an extra try/catch for cross domain requests in Firefox 3
 					try {
-						jQuery.each( headers, function( key, value ) {
-							xhr.setRequestHeader( key, value );
-						} );
+						for ( i in headers ) {
+							xhr.setRequestHeader( i, headers[ i ] );
+						}
 					} catch( _ ) {}
 
 					// Do send the request
@@ -7182,74 +7367,78 @@
 					// Listener
 					callback = function( _, isAbort ) {
 
-						// Was never called and is aborted or complete
-						if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
-
-							// Only called once
-							callback = 0;
-
-							// Do not keep as active anymore
-							if ( handle ) {
-								xhr.onreadystatechange = jQuery.noop;
-								delete xhrs[ handle ];
+						var status,
+							statusText,
+							responseHeaders,
+							responses,
+							xml;
+
+						// Firefox throws exceptions when accessing properties
+						// of an xhr when a network error occured
+						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+						try {
+
+							// Was never called and is aborted or complete
+							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+								// Only called once
+								callback = undefined;
+
+								// Do not keep as active anymore
+								if ( handle ) {
+									xhr.onreadystatechange = jQuery.noop;
+									delete xhrCallbacks[ handle ];
+								}
+
+								// If it's an abort
+								if ( isAbort ) {
+									// Abort it manually if needed
+									if ( xhr.readyState !== 4 ) {
+										xhr.abort();
+									}
+								} else {
+									status = xhr.status;
+									responseHeaders = xhr.getAllResponseHeaders();
+									responses = {};
+									xml = xhr.responseXML;
+
+									// Construct response list
+									if ( xml && xml.documentElement /* #4958 */ ) {
+										responses.xml = xml;
+									}
+									responses.text = xhr.responseText;
+
+									// Firefox throws an exception when accessing
+									// statusText for faulty cross-domain requests
+									try {
+										statusText = xhr.statusText;
+									} catch( e ) {
+										// We normalize with Webkit giving an empty statusText
+										statusText = "";
+									}
+
+									// Filter status for non standard behaviors
+
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									if ( !status && s.isLocal && !s.crossDomain ) {
+										status = responses.text ? 200 : 404;
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									} else if ( status === 1223 ) {
+										status = 204;
+									}
+								}
 							}
-
-							// If it's an abort
-							if ( isAbort ) {
-								// Abort it manually if needed
-								if ( xhr.readyState !== 4 ) {
-									xhr.abort();
-								}
-							} else {
-								// Get info
-								var status = xhr.status,
-									statusText,
-									responseHeaders = xhr.getAllResponseHeaders(),
-									responses = {},
-									xml = xhr.responseXML;
-
-								// Construct response list
-								if ( xml && xml.documentElement /* #4958 */ ) {
-									responses.xml = xml;
-								}
-								responses.text = xhr.responseText;
-
-								// Firefox throws an exception when accessing
-								// statusText for faulty cross-domain requests
-								try {
-									statusText = xhr.statusText;
-								} catch( e ) {
-									// We normalize with Webkit giving an empty statusText
-									statusText = "";
-								}
-
-								// Filter status for non standard behaviours
-								status =
-									// Opera returns 0 when it should be 304
-									// Webkit returns 0 for failing cross-domain no matter the real status
-									status === 0 ?
-										(
-											// Webkit, Firefox: filter out faulty cross-domain requests
-											!s.crossDomain || statusText ?
-											(
-												// Opera: filter out real aborts #6060
-												responseHeaders ?
-												304 :
-												0
-											) :
-											// We assume 302 but could be anything cross-domain related
-											302
-										) :
-										(
-											// IE sometimes returns 1223 when it should be 204 (see #1450)
-											status == 1223 ?
-												204 :
-												status
-										);
-
-								// Call complete
-								complete( status, statusText, responses, responseHeaders );
+						} catch( firefoxAccessException ) {
+							if ( !isAbort ) {
+								complete( -1, firefoxAccessException );
 							}
+						}
+
+						// Call complete if needed
+						if ( responses ) {
+							complete( status, statusText, responses, responseHeaders );
 						}
 					};
 
@@ -7259,10 +7448,15 @@
 					if ( !s.async || xhr.readyState === 4 ) {
 						callback();
 					} else {
-						// Add to list of active xhrs
+						// Create the active xhrs callbacks list if needed
+						// and attach the unload handler
+						if ( !xhrCallbacks ) {
+							xhrCallbacks = {};
+							xhrOnUnloadAbort();
+						}
+						// Add to list of active xhrs callbacks
 						handle = xhrId++;
-						xhrs[ handle ] = xhr;
-						xhr.onreadystatechange = callback;
+						xhr.onreadystatechange = xhrCallbacks[ handle ] = callback;
 					}
 				},
 
@@ -7464,11 +7658,11 @@
 
 				} else {
 					var parts = rfxnum.exec(val),
-						start = e.cur() || 0;
+						start = e.cur();
 
 					if ( parts ) {
 						var end = parseFloat( parts[2] ),
-							unit = parts[3] || "px";
+							unit = parts[3] || ( jQuery.cssNumber[ name ] ? "" : "px" );
 
 						// We need to compute starting value
 						if ( unit !== "px" ) {
@@ -7615,8 +7809,12 @@
 			return this.elem[ this.prop ];
 		}
 
-		var r = parseFloat( jQuery.css( this.elem, this.prop ) );
-		return r || 0;
+		var parsed,
+			r = jQuery.css( this.elem, this.prop );
+		// Empty strings, null, undefined and "auto" are converted to 0,
+		// complex values such as "rotate(1rad)" are returned as is,
+		// simple values such as "10px" are parsed to Float.
+		return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
 	},
 
 	// Start an animation from one number to another
@@ -7627,7 +7825,7 @@
 		this.startTime = jQuery.now();
 		this.start = from;
 		this.end = to;
-		this.unit = unit || this.unit || "px";
+		this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
 		this.now = this.start;
 		this.pos = this.state = 0;
 
@@ -7840,8 +8038,8 @@
 			win = getWindow(doc),
 			clientTop  = docElem.clientTop  || body.clientTop  || 0,
 			clientLeft = docElem.clientLeft || body.clientLeft || 0,
-			scrollTop  = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop ),
-			scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft),
+			scrollTop  = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop,
+			scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
 			top  = box.top  + scrollTop  - clientTop,
 			left = box.left + scrollLeft - clientLeft;
 
@@ -7954,7 +8152,6 @@
 		this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
 
 		body.removeChild( container );
-		body = container = innerDiv = checkDiv = table = td = null;
 		jQuery.offset.initialize = jQuery.noop;
 	},
 
@@ -7984,10 +8181,10 @@
 			curOffset = curElem.offset(),
 			curCSSTop = jQuery.css( elem, "top" ),
 			curCSSLeft = jQuery.css( elem, "left" ),
-			calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1),
+			calculatePosition = (position === "absolute" || position === "fixed") && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1,
 			props = {}, curPosition = {}, curTop, curLeft;
 
-		// need to be able to calculate position if either top or left is auto and position is absolute
+		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
 		if ( calculatePosition ) {
 			curPosition = curElem.position();
 		}
@@ -8078,7 +8275,7 @@
 				if ( win ) {
 					win.scrollTo(
 						!i ? val : jQuery(win).scrollLeft(),
-						 i ? val : jQuery(win).scrollTop()
+						i ? val : jQuery(win).scrollTop()
 					);
 
 				} else {
@@ -8173,5 +8370,6 @@
 });
 
 
+window.jQuery = window.$ = jQuery;
 })(window);
 

--- a/js/jquery-mobile-1.0a3.js
+++ /dev/null
@@ -1,122 +1,1 @@
-/*!
- * jQuery Mobile v1.0a3
- * http://jquerymobile.com/
- *
- * Copyright 2010, jQuery Project
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- */
-(function(a,d){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var g=0,e;(e=b[g])!=null;g++)a(e).triggerHandler("remove");c(b)}}else{var f=a.fn.remove;a.fn.remove=function(b,g){return this.each(function(){if(!g)if(!b||a.filter(b,[this]).length)a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return f.call(a(this),b,g)})}}a.widget=function(b,g,e){var i=b.split(".")[0],h;b=b.split(".")[1];h=i+"-"+b;if(!e){e=g;g=a.Widget}a.expr[":"][h]=function(k){return!!a.data(k,
-b)};a[i]=a[i]||{};a[i][b]=function(k,j){arguments.length&&this._createWidget(k,j)};g=new g;g.options=a.extend(true,{},g.options);a[i][b].prototype=a.extend(true,g,{namespace:i,widgetName:b,widgetEventPrefix:a[i][b].prototype.widgetEventPrefix||b,widgetBaseClass:h},e);a.widget.bridge(b,a[i][b])};a.widget.bridge=function(b,g){a.fn[b]=function(e){var i=typeof e==="string",h=Array.prototype.slice.call(arguments,1),k=this;e=!i&&h.length?a.extend.apply(null,[true,e].concat(h)):e;if(i&&e.charAt(0)==="_")return k;
-i?this.each(function(){var j=a.data(this,b);if(!j)throw"cannot call methods on "+b+" prior to initialization; attempted to call method '"+e+"'";if(!a.isFunction(j[e]))throw"no such method '"+e+"' for "+b+" widget instance";var o=j[e].apply(j,h);if(o!==j&&o!==d){k=o;return false}}):this.each(function(){var j=a.data(this,b);j?j.option(e||{})._init():a.data(this,b,new g(e,this))});return k}};a.Widget=function(b,g){arguments.length&&this._createWidget(b,g)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",
-options:{disabled:false},_createWidget:function(b,g){a.data(g,this.widgetName,this);this.element=a(g);this.options=a.extend(true,{},this.options,this._getCreateOptions(),b);var e=this;this.element.bind("remove."+this.widgetName,function(){e.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){var b={};if(a.metadata)b=a.metadata.get(element)[this.widgetName];return b},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);
-this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(b,g){var e=b;if(arguments.length===0)return a.extend({},this.options);if(typeof b==="string"){if(g===d)return this.options[b];e={};e[b]=g}this._setOptions(e);return this},_setOptions:function(b){var g=this;a.each(b,function(e,i){g._setOption(e,i)});return this},_setOption:function(b,g){this.options[b]=g;if(b===
-"disabled")this.widget()[g?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",g);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(b,g,e){var i=this.options[b];g=a.Event(g);g.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase();e=e||{};if(g.originalEvent){b=a.event.props.length;for(var h;b;){h=a.event.props[--b];g[h]=g.originalEvent[h]}}this.element.trigger(g,
-e);return!(a.isFunction(i)&&i.call(this.element[0],g,e)===false||g.isDefaultPrevented())}}})(jQuery);(function(a,d){a.widget("mobile.widget",{_getCreateOptions:function(){var c=this.element,f={};a.each(this.options,function(b){var g=c.data(b.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()}));if(g!==d)f[b]=g});return f}})})(jQuery);
-(function(a){function d(){var g=c.width(),e=[],i=[],h;f.removeClass("min-width-"+b.join("px min-width-")+"px max-width-"+b.join("px max-width-")+"px");a.each(b,function(k,j){g>=j&&e.push("min-width-"+j+"px");g<=j&&i.push("max-width-"+j+"px")});if(e.length)h=e.join(" ");if(i.length)h+=" "+i.join(" ");f.addClass(h)}var c=a(window),f=a("html"),b=[320,480,768,1024];a.mobile.media=function(){var g={},e=a("<div id='jquery-mediatest'>"),i=a("<body>").append(e);return function(h){if(!(h in g)){var k=document.createElement("style"),
-j="@media "+h+" { #jquery-mediatest { position:absolute; } }";k.type="text/css";if(k.styleSheet)k.styleSheet.cssText=j;else k.appendChild(document.createTextNode(j));f.prepend(i).prepend(k);g[h]=e.css("position")==="absolute";i.add(k).remove()}return g[h]}}();a.mobile.addResolutionBreakpoints=function(g){if(a.type(g)==="array")b=b.concat(g);else b.push(g);b.sort(function(e,i){return e-i});d()};a(document).bind("mobileinit.htmlclass",function(){c.bind("orientationchange.htmlclass resize.htmlclass",
-function(g){g.orientation&&f.removeClass("portrait landscape").addClass(g.orientation);d()})});a(function(){c.trigger("orientationchange.htmlclass")})})(jQuery);
-(function(a,d){function c(h){var k=h.charAt(0).toUpperCase()+h.substr(1);h=(h+" "+g.join(k+" ")+k).split(" ");for(var j in h)if(b[j]!==d)return true}var f=a("<body>").prependTo("html"),b=f[0].style,g=["webkit","moz","o"],e=window.palmGetResource||window.PalmServiceBridge,i=window.blackberry;a.extend(a.support,{orientation:"orientation"in window,touch:"ontouchend"in document,cssTransitions:"WebKitTransitionEvent"in window,pushState:!!history.pushState,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!c("content"),
-boxShadow:!!c("boxShadow")&&!i,scrollTop:("pageXOffset"in window||"scrollTop"in document.documentElement||"scrollTop"in f[0])&&!e,dynamicBaseTag:function(){var h=location.protocol+"//"+location.host+location.pathname+"ui-dir/",k=a("head base"),j=null,o="";if(k.length)o=k.attr("href");else k=j=a("<base>",{href:h}).appendTo("head");var p=a("<a href='testurl'></a>").prependTo(f)[0].href;k[0].href=o?o:location.pathname;j&&j.remove();return p.indexOf(h)===0}()});f.remove();a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")})(jQuery);
-(function(a,d){a.each("touchstart touchmove touchend orientationchange tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(e,i){a.fn[i]=function(h){return h?this.bind(i,h):this.trigger(i)};a.attrFn[i]=true});var c=a.support.touch,f=c?"touchstart":"mousedown",b=c?"touchend":"mouseup",g=c?"touchmove":"mousemove";a.event.special.scrollstart={enabled:true,setup:function(){function e(j,o){h=o;var p=j.type;j.type=h?"scrollstart":"scrollstop";a.event.handle.call(i,j);j.type=
-p}var i=this,h,k;a(i).bind("touchmove scroll",function(j){if(a.event.special.scrollstart.enabled){h||e(j,true);clearTimeout(k);k=setTimeout(function(){e(j,false)},50)}})}};a.event.special.tap={setup:function(){var e=this,i=a(e);i.bind("mousedown touchstart",function(h){function k(n){if(n.type=="scroll")j=true;else{n=n.type=="touchmove"?n.originalEvent.touches[0]:n;if(Math.abs(v[0]-n.pageX)>10||Math.abs(v[1]-n.pageY)>10)j=true}}if(h.which&&h.which!==1||i.data("prevEvent")&&i.data("prevEvent")!==h.type)return false;
-i.data("prevEvent",h.type);setTimeout(function(){i.removeData("prevEvent")},800);var j=false,o=true,p=h.target,t=h.originalEvent,v=h.type=="touchstart"?[t.touches[0].pageX,t.touches[0].pageY]:[h.pageX,h.pageY],m,r;r=setTimeout(function(){if(o&&!j){m=h.type;h.type="taphold";a.event.handle.call(e,h);h.type=m}},750);a(window).one("scroll",k);i.bind("mousemove touchmove",k).one("mouseup touchend",function(n){i.unbind("mousemove touchmove",k);a(window).unbind("scroll",k);clearTimeout(r);o=false;if(!j&&
-p==n.target){m=n.type;n.type="tap";a.event.handle.call(e,n);n.type=m}})})}};a.event.special.swipe={setup:function(){var e=a(this);e.bind(f,function(i){function h(p){if(j){var t=p.originalEvent.touches?p.originalEvent.touches[0]:p;o={time:(new Date).getTime(),coords:[t.pageX,t.pageY]};Math.abs(j.coords[0]-o.coords[0])>10&&p.preventDefault()}}var k=i.originalEvent.touches?i.originalEvent.touches[0]:i,j={time:(new Date).getTime(),coords:[k.pageX,k.pageY],origin:a(i.target)},o;e.bind(g,h).one(b,function(){e.unbind(g,
-h);if(j&&o)if(o.time-j.time<1E3&&Math.abs(j.coords[0]-o.coords[0])>30&&Math.abs(j.coords[1]-o.coords[1])<75)j.origin.trigger("swipe").trigger(j.coords[0]>o.coords[0]?"swipeleft":"swiperight");j=o=d})})}};(function(e){function i(){var o=k();if(o!==j){j=o;h.trigger("orientationchange")}}var h=e(window),k,j;e.event.special.orientationchange={setup:function(){if(e.support.orientation)return false;j=k();h.bind("resize",i)},teardown:function(){if(e.support.orientation)return false;h.unbind("resize",i)},
-add:function(o){var p=o.handler;o.handler=function(t){t.orientation=k();return p.apply(this,arguments)}}};k=function(){var o=document.documentElement;return o&&o.clientWidth/o.clientHeight<1.1?"portrait":"landscape"}})(jQuery);a.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe",swiperight:"swipe"},function(e,i){a.event.special[e]={setup:function(){a(this).bind(i,a.noop)}}})})(jQuery);
-(function(a,d,c){function f(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}var b="hashchange",g=document,e,i=a.event.special,h=g.documentMode,k="on"+b in d&&(h===c||h>7);a.fn[b]=function(j){return j?this.bind(b,j):this.trigger(b)};a.fn[b].delay=50;i[b]=a.extend(i[b],{setup:function(){if(k)return false;a(e.start)},teardown:function(){if(k)return false;a(e.stop)}});e=function(){function j(){var n=f(),u=r(t);if(n!==t){m(t=n,u);a(d).trigger(b)}else if(u!==t)location.href=location.href.replace(/#.*/,
-"")+u;p=setTimeout(j,a.fn[b].delay)}var o={},p,t=f(),v=function(n){return n},m=v,r=v;o.start=function(){p||j()};o.stop=function(){p&&clearTimeout(p);p=c};a.browser.msie&&!k&&function(){var n,u;o.start=function(){if(!n){u=(u=a.fn[b].src)&&u+f();n=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){u||m(f());j()}).attr("src",u||"javascript:0").insertAfter("body")[0].contentWindow;g.onpropertychange=function(){try{if(event.propertyName==="title")n.document.title=g.title}catch(l){}}}};
-o.stop=v;r=function(){return f(n.location.href)};m=function(l,s){var q=n.document,w=a.fn[b].domain;if(l!==s){q.title=g.title;q.open();w&&q.write('<script>document.domain="'+w+'"<\/script>');q.close();n.location.hash=l}}}();return o}()})(jQuery,this);
-(function(a){a.widget("mobile.page",a.mobile.widget,{options:{backBtnText:"Back",addBackBtn:true,degradeInputs:{color:false,date:false,datetime:false,"datetime-local":false,email:false,month:false,number:false,range:"number",search:true,tel:false,time:false,url:false,week:false},keepNative:null},_create:function(){var d=this.element,c=this.options;this.keepNative="[data-role='none'], [data-role='nojs']"+(c.keepNative?", "+c.keepNative:"");if(this._trigger("beforeCreate")!==false){d.find("[data-role='page'], [data-role='content']").andSelf().each(function(){a(this).addClass("ui-"+
-a(this).data("role"))});d.find("[data-role='nojs']").addClass("ui-nojs");d.find("[data-role]").andSelf().each(function(){var f=a(this),b=f.data("role"),g=f.data("theme");if(b==="header"||b==="footer"){f.addClass("ui-bar-"+(g||f.parent("[data-role=page]").data("theme")||"a"));f.attr("role",b==="header"?"banner":"contentinfo");g=f.children("a");var e=g.hasClass("ui-btn-left"),i=g.hasClass("ui-btn-right");if(!e)e=g.eq(0).not(".ui-btn-right").addClass("ui-btn-left").length;i||g.eq(1).addClass("ui-btn-right");
-c.addBackBtn&&b==="header"&&a(".ui-page").length>1&&d.data("url")!==a.mobile.path.stripHash(location.hash)&&!e&&f.data("backbtn")!==false&&a("<a href='#' class='ui-btn-left' data-rel='back' data-icon='arrow-l'>"+c.backBtnText+"</a>").prependTo(f);f.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({tabindex:"0",role:"heading","aria-level":"1"})}else if(b==="content"){g&&f.addClass("ui-body-"+g);f.attr("role","main")}else if(b==="page")f.addClass("ui-body-"+(g||"c"));switch(b){case "header":case "footer":case "page":case "content":f.addClass("ui-"+
-b);break;case "collapsible":case "fieldcontain":case "navbar":case "listview":case "dialog":f[b]()}});this._enhanceControls();d.find("[data-role='button'], .ui-bar > a, .ui-header > a, .ui-footer > a").not(".ui-btn").not(this.keepNative).buttonMarkup();d.find("[data-role='controlgroup']").controlgroup();d.find("a:not(.ui-btn):not(.ui-link-inherit)").not(this.keepNative).addClass("ui-link");d.fixHeaderFooter()}},_enhanceControls:function(){var d=this.options;this.element.find("input").not(this.keepNative).each(function(){var b=
-this.getAttribute("type"),g=d.degradeInputs[b]||"text";d.degradeInputs[b]&&a(this).replaceWith(a("<div>").html(a(this).clone()).html().replace(/type="([a-zA-Z]+)"/,"type="+g+" data-type='$1'"))});var c=this.element.find("input, textarea, select, button"),f=c.not(this.keepNative);c=c.filter("input[type=text]");c.length&&typeof c[0].autocorrect!=="undefined"&&c.each(function(){this.setAttribute("autocorrect","off");this.setAttribute("autocomplete","off")});f.filter("[type='radio'], [type='checkbox']").checkboxradio();
-f.filter("button, [type='button'], [type='submit'], [type='reset'], [type='image']").button();f.filter("input, textarea").not("[type='radio'], [type='checkbox'], [type='button'], [type='submit'], [type='reset'], [type='image'], [type='hidden']").textinput();f.filter("input, select").filter("[data-role='slider'], [data-type='range']").slider();f.filter("select:not([data-role='slider'])").selectmenu()}})})(jQuery);
-(function(a,d,c){a.extend(a.mobile,{subPageUrlKey:"ui-page",nonHistorySelectors:"dialog",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",ajaxEnabled:true,hashListeningEnabled:true,ajaxLinksEnabled:true,ajaxFormsEnabled:true,defaultTransition:"slide",loadingMessage:"loading",metaViewportContent:"width=device-width, minimum-scale=1, maximum-scale=1",gradeA:function(){return a.support.mediaquery},autoInitialize:true,keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,
-COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});a(d.document).trigger("mobileinit");if(a.mobile.gradeA()){var f=a(d),b=a("html"),g=a("head"),e=a.mobile.loadingMessage?a("<div class='ui-loader ui-body-a ui-corner-all'><span class='ui-icon ui-icon-loading spin'></span><h1>"+
-a.mobile.loadingMessage+"</h1></div>"):c;b.addClass("ui-mobile ui-mobile-rendering");a.mobile.metaViewportContent&&a("<meta>",{name:"viewport",content:a.mobile.metaViewportContent}).prependTo(g);a.extend(a.mobile,{pageLoading:function(i){if(i)b.removeClass("ui-loading");else{if(a.mobile.loadingMessage){i=a("."+a.mobile.activeBtnClass).first();e.appendTo(a.mobile.pageContainer).css({top:a.support.scrollTop&&a(d).scrollTop()+a(d).height()/2||i.length&&i.offset().top||100})}b.addClass("ui-loading")}},
-silentScroll:function(i){i=i||0;a.event.special.scrollstart.enabled=false;setTimeout(function(){d.scrollTo(0,i);a(document).trigger("silentscroll",{x:0,y:i})},20);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},initializePage:function(){var i=a("[data-role='page']");i.add("[data-role='dialog']").each(function(){a(this).attr("data-url",a(this).attr("id"))});a.mobile.firstPage=i.first();a.mobile.pageContainer=i.first().parent().addClass("ui-mobile-viewport");a.mobile.pageLoading();
-!a.mobile.hashListeningEnabled||!a.mobile.path.stripHash(location.hash)?a.mobile.changePage(a.mobile.firstPage,false,true,false,true):f.trigger("hashchange",[true])}});a.mobile.autoInitialize&&a(a.mobile.initializePage);f.load(a.mobile.silentScroll)}})(jQuery,this);
-(function(a,d){function c(l){if(i&&(!i.closest(".ui-page-active").length||l))i.removeClass(a.mobile.activeBtnClass);i=null}var f=a(window),b=a("html"),g=a("head"),e={get:function(l){if(l==d)l=location.hash;return e.stripHash(l).replace(/[^\/]*\.[^\/*]+$/,"")},getFilePath:function(l){var s="&"+a.mobile.subPageUrlKey;return l&&l.split(s)[0].split(t)[0]},set:function(l){location.hash=l},origin:"",setOrigin:function(){e.origin=e.get(location.protocol+"//"+location.host+location.pathname)},makeAbsolute:function(l){return e.get()+
-l},clean:function(l){return l.replace(location.protocol+"//"+location.host,"")},stripHash:function(l){return l.replace(/^#/,"")},isExternal:function(l){return e.hasProtocol(e.clean(l))},hasProtocol:function(l){return/^(:?\w+:)/.test(l)},isRelative:function(l){return/^[^\/|#]/.test(l)&&!e.hasProtocol(l)},isEmbeddedPage:function(l){return/^#/.test(l)}},i=null,h={stack:[],activeIndex:0,getActive:function(){return h.stack[h.activeIndex]},getPrev:function(){return h.stack[h.activeIndex-1]},getNext:function(){return h.stack[h.activeIndex+
-1]},addNew:function(l,s){h.getNext()&&h.clearForward();h.stack.push({url:l,transition:s});h.activeIndex=h.stack.length-1},clearForward:function(){h.stack=h.stack.slice(0,h.activeIndex+1)},ignoreNextHashChange:true},k="[tabindex],a,button:visible,select:visible,input",j=null,o=[],p=false,t="&ui-state=dialog",v=g.children("base"),m=location.protocol+"//"+location.host,r=e.get(m+location.pathname),n=r;if(v.length){var u=v.attr("href");if(u)n=u.search(/^[^:/]+:\/\/[^/]+\/?/)==-1?u.charAt(0)=="/"?m+u:
-r+u:u;n+=n.charAt(n.length-1)=="/"?" ":"/"}base=a.support.dynamicBaseTag?{element:v.length?v:a("<base>",{href:n}).prependTo(g),set:function(l){base.element.attr("href",n+e.get(l))},reset:function(){base.element.attr("href",n)}}:d;e.setOrigin();a.fn.animationComplete=function(l){if(a.support.cssTransitions)return a(this).one("webkitAnimationEnd",l);else setTimeout(l,0)};a.mobile.updateHash=e.set;a.mobile.path=e;a.mobile.base=base;a.mobile.urlstack=h.stack;a.mobile.urlHistory=h;a.mobile.changePage=
-function(l,s,q,w,z){function F(){function A(){if(w!==false&&x){h.ignoreNextHashChange=false;e.set(x)}!I&&!K&&h.addNew(x,s);c();a.mobile.silentScroll(l.data("lastScroll"));var G=l,P=G.find(".ui-title:eq(0)");P.length?P.focus():G.find(k).eq(0).focus();y&&y.data("page")._trigger("hide",null,{nextPage:l});l.data("page")._trigger("show",null,{prevPage:y||a("")});a.mobile.activePage=l;L!=null&&L.remove();b.removeClass("ui-mobile-rendering");p=false;o.length>0&&a.mobile.changePage.apply(a.mobile,o.pop())}
-function B(G){a.mobile.pageContainer.addClass(G);D.push(G)}a.mobile.silentScroll();var M=f.scrollTop(),J=["flip"],D=[];if(x.indexOf("&"+a.mobile.subPageUrlKey)>-1)l=a("[data-url='"+x+"']");if(y){y.data("lastScroll",M);y.data("page")._trigger("beforehide",{nextPage:l})}l.data("page")._trigger("beforeshow",{prevPage:y||a("")});if(s&&s!=="none"){a.mobile.pageLoading(true);a.inArray(s,J)>=0&&B("ui-mobile-viewport-perspective");B("ui-mobile-viewport-transitioning");if(y)y.addClass(s+" out "+(q?"reverse":
-""));l.addClass(a.mobile.activePageClass+" "+s+" in "+(q?"reverse":""));l.animationComplete(function(){y.add(l).removeClass("out in reverse "+s);y&&y.removeClass(a.mobile.activePageClass);A();a.mobile.pageContainer.removeClass(D.join(" "));D=[]})}else{a.mobile.pageLoading(true);y&&y.removeClass(a.mobile.activePageClass);l.addClass(a.mobile.activePageClass);A()}}function Q(){if(j||l.data("role")=="dialog"){x=h.getActive().url+t;if(j){l.attr("data-role",j);j=null}}l.page()}var E=a.type(l)==="array",
-H=a.type(l)==="object",y=E?l[0]:a.mobile.activePage;l=E?l[1]:l;var x=fileUrl=a.type(l)==="string"?e.stripHash(l):"",C=d,N="get",R=false,L=null,O=h.getActive(),I=false,K=false;if(!(O&&h.stack.length>1&&O.url===x&&!E&&!H))if(p)o.unshift(arguments);else{p=true;if(z){a.each(h.stack,function(A){if(this.url==x){urlIndex=A;I=A<h.activeIndex;K=!I;h.activeIndex=A}});if(I){q=true;s=s||O.transition}else if(K)s=s||h.getActive().transition}if(H&&l.url){x=l.url;C=l.data;N=l.type;R=true;if(C&&N=="get"){if(a.type(C)==
-"object")C=a.param(C);x+="?"+C;C=d}}base&&base.reset();a(window.document.activeElement).add("input:focus, textarea:focus, select:focus").blur();if(x){l=a("[data-url='"+x+"']");fileUrl=e.getFilePath(x)}else{E=l.attr("data-url");H=e.getFilePath(E);if(E!=H)fileUrl=H}if(s===d)s=a.mobile.defaultTransition;if(l.length&&!R){fileUrl&&base&&base.set(fileUrl);Q();F()}else{if(l.length)L=l;a.mobile.pageLoading();a.ajax({url:fileUrl,type:N,data:C,success:function(A){var B=/ data-url="(.*)"/.test(A)&&RegExp.$1;
-if(B){base&&base.set(B);x=fileUrl=e.getFilePath(B)}else base&&base.set(fileUrl);B=a("<div></div>");B.get(0).innerHTML=A;l=B.find('[data-role="page"], [data-role="dialog"]').first();if(!a.support.dynamicBaseTag){var M=e.get(fileUrl);l.find("[src],link[href]").each(function(){var J=a(this).is("[href]")?"href":"src",D=a(this).attr(J);D.replace(location.protocol+"//"+location.host+location.pathname,"");/^(\w+:|#|\/)/.test(D)||a(this).attr(J,M+D)})}l.attr("data-url",fileUrl).appendTo(a.mobile.pageContainer);
-Q();setTimeout(function(){F()},0)},error:function(){a.mobile.pageLoading(true);c(true);base&&base.set(e.get());a("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>Error Loading Page</h1></div>").css({display:"block",opacity:0.96,top:a(window).scrollTop()+100}).appendTo(a.mobile.pageContainer).delay(800).fadeOut(400,function(){a(this).remove()})}})}}};a("form[data-ajax!='false']").live("submit",function(l){if(a.mobile.ajaxEnabled&&a.mobile.ajaxFormsEnabled){var s=a(this).attr("method"),
-q=e.clean(a(this).attr("action"));if(!e.isExternal(q)){if(e.isRelative(q))q=e.makeAbsolute(q);a.mobile.changePage({url:q,type:s,data:a(this).serialize()},d,d,true);l.preventDefault()}}});a("a").live("click",function(l){var s=a(this),q=s.attr("href")||"#";q=e.clean(q);var w=s.is("[rel='external']"),z=e.isEmbeddedPage(q);w=e.isExternal(q)||w&&!z;z=s.is("[target]");var F=s.is("[data-ajax='false']");if(s.is("[data-rel='back']")){window.history.back();return false}if(q==="#")return false;i=s.closest(".ui-btn").addClass(a.mobile.activeBtnClass);
-if(w||F||z||!a.mobile.ajaxEnabled||!a.mobile.ajaxLinksEnabled){c(true);if(z)window.open(q);else if(F)return;else location.href=q}else{w=s.data("transition");z=(z=s.data("direction"))&&z=="reverse"||s.data("back");j=s.attr("data-rel");if(e.isRelative(q))q=e.makeAbsolute(q);q=e.stripHash(q);a.mobile.changePage(q,w,z)}l.preventDefault()});f.bind("hashchange",function(){var l=e.stripHash(location.hash),s=a.mobile.urlHistory.stack.length===0?false:d;if(!a.mobile.hashListeningEnabled||!h.ignoreNextHashChange||
-h.stack.length>1&&l.indexOf(t)>-1&&!a.mobile.activePage.is(".ui-dialog")){if(!h.ignoreNextHashChange)h.ignoreNextHashChange=true}else l?a.mobile.changePage(l,s,d,false,true):a.mobile.changePage(a.mobile.firstPage,s,true,false,true)})})(jQuery);
-(function(a,d){a.fn.fixHeaderFooter=function(){if(!a.support.scrollTop)return this;return this.each(function(){var c=a(this);c.data("fullscreen")&&c.addClass("ui-page-fullscreen");c.find('.ui-header[data-position="fixed"]').addClass("ui-header-fixed ui-fixed-inline fade");c.find('.ui-footer[data-position="fixed"]').addClass("ui-footer-fixed ui-fixed-inline fade")})};a.fixedToolbars=function(){function c(){if(!e&&g=="overlay"){i||a.fixedToolbars.hide(true);a.fixedToolbars.startShowTimer()}}function f(m){var r=
-0;if(m){var n=m.offsetParent,u=document.body;for(r=m.offsetTop;m&&m!=u;){r+=m.scrollTop||0;if(m==n){r+=n.offsetTop;n=m.offsetParent}m=m.parentNode}}return r}function b(m){var r=a(window).scrollTop(),n=f(m[0]),u=m.css("top")=="auto"?0:parseFloat(m.css("top")),l=window.innerHeight,s=m.outerHeight(),q=m.parents(".ui-page:not(.ui-page-fullscreen)").length;if(m.is(".ui-header-fixed")){u=r-n+u;if(u<n)u=0;return m.css("top",q?u:r)}else{u=r+l-s-(n-u);return m.css("top",q?u:r+l-s)}}if(a.support.scrollTop){var g=
-"inline",e=false,i,h,k=a.support.touch,j=k?"touchstart":"mousedown",o=k?"touchend":"mouseup",p=null,t=false,v=true;a(function(){a(document).bind(j,function(m){if(v)a(m.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length||(p=g)}).bind(o,function(m){if(v)if(!a(m.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length)if(!t){a.fixedToolbars.toggle(p);p=null}}).bind("scrollstart",function(m){if(!a(m.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length){t=
-true;if(p==null)p=g;if(e=(m=p=="overlay")||!!i){a.fixedToolbars.clearShowTimer();m&&a.fixedToolbars.hide(true)}}}).bind("scrollstop",function(m){if(!a(m.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length){t=false;if(e){e=false;a.fixedToolbars.startShowTimer()}p=null}}).bind("silentscroll",c);a(window).bind("resize",c)});a(".ui-page").live("pagebeforeshow",function(m){m=a(m.target).find('[data-role="footer"]:not(.ui-sticky-footer)');var r=m.data("id");
-h=null;if(r){h=a('.ui-footer[data-id="'+r+'"].ui-sticky-footer');if(h.length==0){h=m;m=h.clone();h.addClass("ui-sticky-footer").before(m)}m.addClass("ui-footer-duplicate");h.appendTo(a.pageContainer).css("top",0);b(h)}});a(".ui-page").live("pageshow",function(m){h&&h.length&&h.appendTo(m.target).css("top",0);a.fixedToolbars.show(true,this)});return{show:function(m,r){a.fixedToolbars.clearShowTimer();g="overlay";return(r?a(r):a.mobile.activePage?a.mobile.activePage:a(".ui-page-active")).children(".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last").each(function(){var n=
-a(this),u=a(window).scrollTop(),l=f(n[0]),s=window.innerHeight,q=n.outerHeight();u=n.is(".ui-header-fixed")&&u<=l+q||n.is(".ui-footer-fixed")&&l<=u+s;n.addClass("ui-fixed-overlay").removeClass("ui-fixed-inline");!u&&!m&&n.animationComplete(function(){n.removeClass("in")}).addClass("in");b(n)})},hide:function(m){g="inline";return(a.mobile.activePage?a.mobile.activePage:a(".ui-page-active")).children(".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last").each(function(){var r=a(this),
-n=r.css("top");n=n=="auto"?0:parseFloat(n);r.addClass("ui-fixed-inline").removeClass("ui-fixed-overlay");if(n<0||r.is(".ui-header-fixed")&&n!=0)if(m)r.css("top",0);else r.css("top")!=="auto"&&parseFloat(r.css("top"))!==0&&r.animationComplete(function(){r.removeClass("out reverse");r.css("top",0)}).addClass("out reverse")})},startShowTimer:function(){a.fixedToolbars.clearShowTimer();var m=a.makeArray(arguments);i=setTimeout(function(){i=d;a.fixedToolbars.show.apply(null,m)},100)},clearShowTimer:function(){i&&
-clearTimeout(i);i=d},toggle:function(m){if(m)g=m;return g=="overlay"?a.fixedToolbars.hide():a.fixedToolbars.show()},setTouchToggleEnabled:function(m){v=m}}}}()})(jQuery);
-(function(a,d){a.widget("mobile.checkboxradio",a.mobile.widget,{options:{theme:null},_create:function(){var c=this,f=this.element,b=f.closest("form,fieldset,[data-role='page']").find("label[for='"+f.attr("id")+"']"),g=f.attr("type"),e="ui-icon-"+g+"-off";if(!(g!="checkbox"&&g!="radio")){if(!this.options.theme)this.options.theme=this.element.data("theme");b.buttonMarkup({theme:this.options.theme,icon:this.element.parents("[data-type='horizontal']").length?d:e,shadow:false});f.add(b).wrapAll("<div class='ui-"+
-g+"'></div>");b.bind({mouseover:function(){if(a(this).parent().is(".ui-disabled"))return false},touchmove:function(i){i=i.originalEvent.touches[0];if(b.data("movestart")){if(Math.abs(b.data("movestart")[0]-i.pageX)>10||Math.abs(abel.data("movestart")[1]-i.pageY)>10)b.data("moved",true)}else b.data("movestart",[parseFloat(i.pageX),parseFloat(i.pageY)])},"touchend mouseup":function(i){b.removeData("movestart");if(b.data("etype")&&b.data("etype")!==i.type||b.data("moved")){b.removeData("etype").removeData("moved");
-b.data("moved")&&b.removeData("moved");return false}b.data("etype",i.type);c._cacheVals();f.attr("checked",g==="radio"&&true||!f.is(":checked"));c._updateAll();i.preventDefault()},click:false});f.bind({mousedown:function(){this._cacheVals()},click:function(){c._updateAll()},focus:function(){b.addClass("ui-focus")},blur:function(){b.removeClass("ui-focus")}});this.refresh()}},_cacheVals:function(){this._getInputSet().each(function(){a(this).data("cacheVal",a(this).is(":checked"))})},_getInputSet:function(){return this.element.closest("form,fieldset,[data-role='page']").find("input[name='"+
-this.element.attr("name")+"'][type='"+this.element.attr("type")+"']")},_updateAll:function(){this._getInputSet().each(function(){var c=a(this).data("cacheVal");if(c&&c!==a(this).is(":checked")||a(this).is("[type='checkbox']"))a(this).trigger("change")}).checkboxradio("refresh")},refresh:function(){var c=this.element,f=c.closest("form,fieldset,[data-role='page']").find("label[for='"+c.attr("id")+"']"),b=c.attr("type"),g=f.find(".ui-icon"),e="ui-icon-"+b+"-on";b="ui-icon-"+b+"-off";if(c[0].checked){f.addClass("ui-btn-active");
-g.addClass(e);g.removeClass(b)}else{f.removeClass("ui-btn-active");g.removeClass(e);g.addClass(b)}c.is(":disabled")?this.disable():this.enable()},disable:function(){this.element.attr("disabled",true).parent().addClass("ui-disabled")},enable:function(){this.element.attr("disabled",false).parent().removeClass("ui-disabled")}})})(jQuery);
-(function(a){a.widget("mobile.textinput",a.mobile.widget,{options:{theme:null},_create:function(){var d=this.element,c=this.options,f=c.theme;if(!f){f=this.element.closest("[class*='ui-bar-'],[class*='ui-body-']");f=f.length?/ui-(bar|body)-([a-z])/.exec(f.attr("class"))[2]:"c"}f=" ui-body-"+f;a("label[for="+d.attr("id")+"]").addClass("ui-input-text");d.addClass("ui-input-text ui-body-"+c.theme);var b=d;if(d.is('[type="search"],[data-type="search"]')){b=d.wrap('<div class="ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield'+
-f+'"></div>').parent();var g=a('<a href="#" class="ui-input-clear" title="clear text">clear text</a>').tap(function(h){d.val("").focus();d.trigger("change");g.addClass("ui-input-clear-hidden");h.preventDefault()}).appendTo(b).buttonMarkup({icon:"delete",iconpos:"notext",corners:true,shadow:true});c=function(){d.val()==""?g.addClass("ui-input-clear-hidden"):g.removeClass("ui-input-clear-hidden")};c();d.keyup(c)}else d.addClass("ui-corner-all ui-shadow-inset"+f);d.focus(function(){b.addClass("ui-focus")}).blur(function(){b.removeClass("ui-focus")});
-if(d.is("textarea")){var e=function(){var h=d[0].scrollHeight;d[0].clientHeight<h&&d.css({height:h+15})},i;d.keyup(function(){clearTimeout(i);i=setTimeout(e,100)})}},disable:function(){(this.element.attr("disabled",true).is('[type="search"],[data-type="search"]')?this.element.parent():this.element).addClass("ui-disabled")},enable:function(){(this.element.attr("disabled",false).is('[type="search"],[data-type="search"]')?this.element.parent():this.element).removeClass("ui-disabled")}})})(jQuery);
-(function(a){a.widget("mobile.selectmenu",a.mobile.widget,{options:{theme:null,disabled:false,icon:"arrow-d",iconpos:"right",inline:null,corners:true,shadow:true,iconshadow:true,menuPageTheme:"b",overlayTheme:"a",hidePlaceholderMenuItems:true,closeText:"Close",nativeMenu:false},_create:function(){var d=this,c=this.options,f=this.element.wrap("<div class='ui-select'>"),b=f.attr("id"),g=a("label[for="+b+"]").addClass("ui-select"),e=(d.options.nativeMenu?a("<div/>"):a("<a>",{href:"#",role:"button",id:k,
-"aria-haspopup":"true","aria-owns":j})).text(a(f[0].options.item(f[0].selectedIndex)).text()).insertBefore(f).buttonMarkup({theme:c.theme,icon:c.icon,iconpos:c.iconpos,inline:c.inline,corners:c.corners,shadow:c.shadow,iconshadow:c.iconshadow}),i=d.isMultiple=f[0].multiple;c.nativeMenu&&window.opera&&window.opera.version&&f.addClass("ui-select-nativeonly");if(!c.nativeMenu){var h=f.find("option"),k=b+"-button",j=b+"-menu",o=f.closest(".ui-page"),p=/ui-btn-up-([a-z])/.exec(e.attr("class"))[1],t=a("<div data-role='dialog' data-theme='"+
-c.menuPageTheme+"'><div data-role='header'><div class='ui-title'>"+g.text()+"</div></div><div data-role='content'></div></div>").appendTo(a.mobile.pageContainer).page(),v=t.find(".ui-content"),m=t.find(".ui-header a"),r=a("<div>",{"class":"ui-selectmenu-screen ui-screen-hidden"}).appendTo(o),n=a("<div>",{"class":"ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all pop ui-body-"+c.overlayTheme}).insertAfter(r),u=a("<ul>",{"class":"ui-selectmenu-list",id:j,role:"listbox","aria-labelledby":k,
-"data-theme":p}).appendTo(n);p=a("<div>",{"class":"ui-header ui-bar-"+p}).prependTo(n);var l=a("<h1>",{"class":"ui-title"}).appendTo(p),s=a("<a>",{"data-iconpos":"notext","data-icon":"delete",text:c.closeText,href:"#","class":"ui-btn-left"}).appendTo(p).buttonMarkup()}if(i)d.buttonCount=a("<span>").addClass("ui-li-count ui-btn-up-c ui-btn-corner-all").hide().appendTo(e);c.disabled&&this.disable();f.change(function(){d.refresh()});a.extend(d,{select:f,optionElems:h,selectID:b,label:g,buttonId:k,menuId:j,
-thisPage:o,button:e,menuPage:t,menuPageContent:v,screen:r,listbox:n,list:u,menuType:void 0,header:p,headerClose:s,headerTitle:l,placeholder:""});if(c.nativeMenu){f.appendTo(e).bind("touchstart mousedown",function(){e.addClass(a.mobile.activeBtnClass)}).bind("focus mouseover",function(){e.trigger("mouseover")}).bind("touchmove",function(){e.removeClass(a.mobile.activeBtnClass)}).bind("change blur mouseout",function(){e.trigger("mouseout").removeClass(a.mobile.activeBtnClass)});e.attr("tabindex","-1")}else{d.refresh();
-f.attr("tabindex","-1").focus(function(){a(this).blur();e.focus()});e.bind("touchstart",function(q){a(this).data("startTouches",a.extend({},q.originalEvent.touches[0]))}).bind(a.support.touch?"touchend":"mouseup",function(q){a(this).data("moved")?a(this).removeData("moved"):d.open();q.preventDefault()}).bind("touchmove",function(q){q=q.originalEvent.touches[0];var w=a(this).data("startTouches"),z=Math.abs(q.pageY-w.pageY);if(Math.abs(q.pageX-w.pageX)>10||z>10)a(this).data("moved",true)});u.delegate("li:not(.ui-disabled, .ui-li-divider)",
-"click",function(q){if(a(q.target).is("a")){var w=u.find("li:not(.ui-li-divider)").index(this);w=d.optionElems.eq(w)[0];w.selected=i?!w.selected:true;i&&a(this).find(".ui-icon").toggleClass("ui-icon-checkbox-on",w.selected).toggleClass("ui-icon-checkbox-off",!w.selected);f.trigger("change");i||d.close();q.preventDefault()}});r.add(s).add(m).bind("click",function(q){d.close();q.preventDefault();a.contains(m[0],q.target)&&q.stopImmediatePropagation()})}},_buildList:function(){var d=this,c=this.options,
-f=this.placeholder,b=[],g=[],e=d.isMultiple?"checkbox-off":"false";d.list.empty().filter(".ui-listview").listview("destroy");d.select.find("option").each(function(){var i=a(this),h=i.parent(),k=i.text(),j="<a href='#'>"+k+"</a>",o=[],p=[];if(h.is("optgroup")){h=h.attr("label");if(a.inArray(h,b)===-1){g.push("<li data-role='list-divider'>"+h+"</li>");b.push(h)}}if(!this.getAttribute("value")||k.length==0||i.data("placeholder")){c.hidePlaceholderMenuItems&&o.push("ui-selectmenu-placeholder");f=d.placeholder=
-k}if(this.disabled){o.push("ui-disabled");p.push("aria-disabled='true'")}g.push("<li data-icon='"+e+"' class='"+o.join(" ")+"' "+p.join(" ")+">"+j+"</li>")});d.list.html(g.join(" "));this.isMultiple||this.headerClose.hide();!this.isMultiple&&!f.length?this.header.hide():this.headerTitle.text(this.placeholder);d.list.listview()},refresh:function(d){var c=this,f=this.element,b=this.isMultiple,g=this.optionElems=f.find("option"),e=g.filter(":selected"),i=e.map(function(){return g.index(this)}).get();
-if(!c.options.nativeMenu&&(d||f[0].options.length>c.list.find("li").length))c._buildList();c.button.find(".ui-btn-text").text(function(){if(!b)return e.text();return e.length?e.map(function(){return a(this).text()}).get().join(", "):c.placeholder});if(b)c.buttonCount[e.length>1?"show":"hide"]().text(e.length);c.options.nativeMenu||c.list.find("li:not(.ui-li-divider)").removeClass(a.mobile.activeBtnClass).attr("aria-selected",false).each(function(h){if(a.inArray(h,i)>-1){h=a(this).addClass(a.mobile.activeBtnClass);
-h.find("a").attr("aria-selected",true);b&&h.find(".ui-icon").removeClass("ui-icon-checkbox-off").addClass("ui-icon-checkbox-on")}})},open:function(){function d(){c.list.find(".ui-btn-active").focus()}if(!(this.options.disabled||this.options.nativeMenu)){var c=this,f=c.list.outerHeight(),b=c.list.outerWidth(),g=a(window).scrollTop(),e=c.button.offset().top,i=window.innerHeight,h=c.list.parents(".ui-dialog").length;c.button.addClass(a.mobile.activeBtnClass);if(h||f>i-80||!a.support.scrollTop){g==0&&
-e>i&&c.thisPage.one("pagehide",function(){a(this).data("lastScroll",e)});c.menuPage.one("pageshow",function(){a(window).one("silentscroll",function(){d()})});c.menuType="page";c.menuPageContent.append(c.list);a.mobile.changePage(c.menuPage,"pop",false,true)}else{c.menuType="overlay";c.screen.height(a(document).height()).removeClass("ui-screen-hidden");h=e-g;var k=g+i-e,j=f/2;f=h>f/2&&k>f/2?e+c.button.outerHeight()/2-j:h>k?g+i-f-30:g+30;b=c.button.offset().left+c.button.outerWidth()/2-b/2;c.listbox.append(c.list).removeClass("ui-selectmenu-hidden").css({top:f,
-left:b}).addClass("in");d()}setTimeout(function(){c.isOpen=true},400)}},close:function(){function d(){setTimeout(function(){c.button.focus();c.button.removeClass(a.mobile.activeBtnClass)},40);c.listbox.removeAttr("style").append(c.list)}if(!(this.options.disabled||!this.isOpen||this.options.nativeMenu)){var c=this;if(c.menuType=="page"){a.mobile.changePage([c.menuPage,c.thisPage],"pop",true,false);c.menuPage.one("pagehide",d)}else{c.screen.addClass("ui-screen-hidden");c.listbox.addClass("ui-selectmenu-hidden").removeAttr("style").removeClass("in");
-d()}this.isOpen=false}},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)}})})(jQuery);
-(function(a){a.fn.buttonMarkup=function(c){return this.each(function(){var f=a(this),b=a.extend({},a.fn.buttonMarkup.defaults,f.data(),c),g,e="ui-btn-inner",i;d&&d();if(!b.theme){g=f.closest("[class*='ui-bar-'],[class*='ui-body-']");b.theme=g.length?/ui-(bar|body)-([a-z])/.exec(g.attr("class"))[2]:"c"}g="ui-btn ui-btn-up-"+b.theme;if(b.inline)g+=" ui-btn-inline";if(b.icon){b.icon="ui-icon-"+b.icon;b.iconpos=b.iconpos||"left";i="ui-icon "+b.icon;if(b.shadow)i+=" ui-icon-shadow"}if(b.iconpos){g+=" ui-btn-icon-"+
-b.iconpos;b.iconpos=="notext"&&!f.attr("title")&&f.attr("title",f.text())}if(b.corners){g+=" ui-btn-corner-all";e+=" ui-btn-corner-all"}if(b.shadow)g+=" ui-shadow";f.attr("data-theme",b.theme).addClass(g);b=("<D class='"+e+"'><D class='ui-btn-text'></D>"+(b.icon?"<span class='"+i+"'></span>":"")+"</D>").replace(/D/g,b.wrapperEls);f.wrapInner(b)})};a.fn.buttonMarkup.defaults={corners:true,shadow:true,iconshadow:true,wrapperEls:"span"};var d=function(){a(".ui-btn:not(.ui-disabled)").live({"touchstart mousedown":function(){var c=
-a(this).attr("data-theme");a(this).removeClass("ui-btn-up-"+c).addClass("ui-btn-down-"+c)},"touchmove touchend mouseup":function(){var c=a(this).attr("data-theme");a(this).removeClass("ui-btn-down-"+c).addClass("ui-btn-up-"+c)},"mouseover focus":function(){var c=a(this).attr("data-theme");a(this).removeClass("ui-btn-up-"+c).addClass("ui-btn-hover-"+c)},"mouseout blur":function(){var c=a(this).attr("data-theme");a(this).removeClass("ui-btn-hover-"+c).addClass("ui-btn-up-"+c)}});d=null}})(jQuery);
-(function(a){a.widget("mobile.button",a.mobile.widget,{options:{theme:null,icon:null,iconpos:null,inline:null,corners:true,shadow:true,iconshadow:true},_create:function(){var d=this.element,c=this.options;this.button=a("<div></div>").text(d.text()||d.val()).buttonMarkup({theme:c.theme,icon:c.icon,iconpos:c.iconpos,inline:c.inline,corners:c.corners,shadow:c.shadow,iconshadow:c.iconshadow}).insertBefore(d).append(d.addClass("ui-btn-hidden"));d.attr("type")!=="reset"&&d.click(function(){var f=a("<input>",
-{type:"hidden",name:d.attr("name"),value:d.attr("value")}).insertBefore(d);a(document).submit(function(){f.remove()})})},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)}})})(jQuery);
-(function(a){a.widget("mobile.slider",a.mobile.widget,{options:{theme:null,trackTheme:null,disabled:false},_create:function(){var d=this,c=this.element,f=c.parents("[class*=ui-bar-],[class*=ui-body-]").eq(0);f=f.length?f.attr("class").match(/ui-(bar|body)-([a-z])/)[2]:"c";var b=this.options.theme?this.options.theme:f,g=this.options.trackTheme?this.options.trackTheme:f,e=c[0].nodeName.toLowerCase();f=e=="select"?"ui-slider-switch":"";var i=c.attr("id"),h=i+"-label";i=a("[for="+i+"]").attr("id",h);
-var k=function(){return e=="input"?parseFloat(c.val()):c[0].selectedIndex},j=e=="input"?parseFloat(c.attr("min")):0,o=e=="input"?parseFloat(c.attr("max")):c.find("option").length-1,p=window.parseFloat(c.attr("step")||1),t=a('<div class="ui-slider '+f+" ui-btn-down-"+g+' ui-btn-corner-all" role="application"></div>'),v=a('<a href="#" class="ui-slider-handle"></a>').appendTo(t).buttonMarkup({corners:true,theme:b,shadow:true}).attr({role:"slider","aria-valuemin":j,"aria-valuemax":o,"aria-valuenow":k(),
-"aria-valuetext":k(),title:k(),"aria-labelledby":h});a.extend(this,{slider:t,handle:v,dragging:false,beforeStart:null});if(e=="select"){t.wrapInner('<div class="ui-slider-inneroffset"></div>');c.find("option");c.find("option").each(function(m){var r=m==0?"b":"a",n=m==0?"right":"left";m=m==0?" ui-btn-down-"+g:" ui-btn-active";a('<div class="ui-slider-labelbg ui-slider-labelbg-'+r+m+" ui-btn-corner-"+n+'"></div>').prependTo(t);a('<span class="ui-slider-label ui-slider-label-'+r+m+" ui-btn-corner-"+
-n+'" role="img">'+a(this).text()+"</span>").prependTo(v)})}i.addClass("ui-slider");c.addClass(e=="input"?"ui-slider-input":"ui-slider-switch").change(function(){d.refresh(k(),true)}).keyup(function(){d.refresh(k(),true,true)}).blur(function(){d.refresh(k(),true)});a(document).bind("touchmove mousemove",function(m){if(d.dragging){d.refresh(m);return false}});t.bind("touchstart mousedown",function(m){d.dragging=true;if(e==="select")d.beforeStart=c[0].selectedIndex;d.refresh(m);return false});t.add(document).bind("touchend mouseup",
-function(){if(d.dragging){d.dragging=false;if(e==="select"){if(d.beforeStart===c[0].selectedIndex)d.refresh(d.beforeStart===0?1:0);var m=k();m=Math.round(m/(o-j)*100);v.addClass("ui-slider-handle-snapping").css("left",m+"%").animationComplete(function(){v.removeClass("ui-slider-handle-snapping")})}return false}});t.insertAfter(c);this.handle.bind("touchstart mousedown",function(){a(this).focus()});this.handle.bind("keydown",function(m){var r=k();if(!d.options.disabled){switch(m.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:m.preventDefault();
-if(!d._keySliding){d._keySliding=true;a(this).addClass("ui-state-active")}}switch(m.keyCode){case a.mobile.keyCode.HOME:d.refresh(j);break;case a.mobile.keyCode.END:d.refresh(o);break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:d.refresh(r+p);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:d.refresh(r-p)}}}).keyup(function(){if(d._keySliding){d._keySliding=false;a(this).removeClass("ui-state-active")}});this.refresh()},
-refresh:function(d,c,f){if(!this.options.disabled){var b=this.element,g=b[0].nodeName.toLowerCase(),e=g==="input"?parseFloat(b.attr("min")):0,i=g==="input"?parseFloat(b.attr("max")):b.find("option").length-1;if(typeof d==="object"){d=d.originalEvent.touches?d.originalEvent.touches[0]:d;if(!this.dragging||d.pageX<this.slider.offset().left-8||d.pageX>this.slider.offset().left+this.slider.width()+8)return;d=Math.round((d.pageX-this.slider.offset().left)/this.slider.width()*100)}else{if(d==null)d=g===
-"input"?parseFloat(b.val()):b[0].selectedIndex;d=(parseFloat(d)-e)/(i-e)*100}if(!isNaN(d)){if(d<0)d=0;if(d>100)d=100;var h=Math.round(d/100*(i-e))+e;if(h<e)h=e;if(h>i)h=i;this.handle.css("left",d+"%");this.handle.attr({"aria-valuenow":g==="input"?h:b.find("option").eq(h).attr("value"),"aria-valuetext":g==="input"?h:b.find("option").eq(h).text(),title:h});if(g==="select")h===0?this.slider.addClass("ui-slider-switch-a").removeClass("ui-slider-switch-b"):this.slider.addClass("ui-slider-switch-b").removeClass("ui-slider-switch-a");
-if(!f){if(g==="input")b.val(h);else b[0].selectedIndex=h;c||b.trigger("change")}}}},enable:function(){this.element.attr("disabled",false);this.slider.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.slider.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)}})})(jQuery);
-(function(a){a.widget("mobile.collapsible",a.mobile.widget,{options:{expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsed:false,heading:">:header,>legend",theme:null,iconTheme:"d"},_create:function(){var d=this.element,c=this.options,f=d.addClass("ui-collapsible-contain"),b=d.find(c.heading).eq(0),g=f.wrapInner('<div class="ui-collapsible-content"></div>').find(".ui-collapsible-content");d=d.closest('[data-role="collapsible-set"]').addClass("ui-collapsible-set");
-if(b.is("legend")){b=a('<div role="heading">'+b.html()+"</div>").insertBefore(b);b.next().remove()}b.insertBefore(g);b.addClass("ui-collapsible-heading").append('<span class="ui-collapsible-heading-status"></span>').wrapInner('<a href="#" class="ui-collapsible-heading-toggle"></a>').find("a:eq(0)").buttonMarkup({shadow:!!!d.length,corners:false,iconPos:"left",icon:"plus",theme:c.theme}).find(".ui-icon").removeAttr("class").buttonMarkup({shadow:true,corners:true,iconPos:"notext",icon:"plus",theme:c.iconTheme});
-if(d.length)f.data("collapsible-last")&&b.find("a:eq(0), .ui-btn-inner").addClass("ui-corner-bottom");else b.find("a:eq(0)").addClass("ui-corner-all").find(".ui-btn-inner").addClass("ui-corner-all");f.bind("collapse",function(e){if(!e.isDefaultPrevented()){e.preventDefault();b.addClass("ui-collapsible-heading-collapsed").find(".ui-collapsible-heading-status").text(c.expandCueText);b.find(".ui-icon").removeClass("ui-icon-minus").addClass("ui-icon-plus");g.addClass("ui-collapsible-content-collapsed").attr("aria-hidden",
-true);f.data("collapsible-last")&&b.find("a:eq(0), .ui-btn-inner").addClass("ui-corner-bottom")}}).bind("expand",function(e){if(!e.isDefaultPrevented()){e.preventDefault();b.removeClass("ui-collapsible-heading-collapsed").find(".ui-collapsible-heading-status").text(c.collapseCueText);b.find(".ui-icon").removeClass("ui-icon-plus").addClass("ui-icon-minus");g.removeClass("ui-collapsible-content-collapsed").attr("aria-hidden",false);f.data("collapsible-last")&&b.find("a:eq(0), .ui-btn-inner").removeClass("ui-corner-bottom")}}).trigger(c.collapsed?
-"collapse":"expand");if(d.length&&!d.data("collapsiblebound")){d.data("collapsiblebound",true).bind("expand",function(e){a(this).find(".ui-collapsible-contain").not(a(e.target).closest(".ui-collapsible-contain")).not("> .ui-collapsible-contain .ui-collapsible-contain").trigger("collapse")});d=d.find("[data-role=collapsible]");d.first().find("a:eq(0)").addClass("ui-corner-top").find(".ui-btn-inner").addClass("ui-corner-top");d.last().data("collapsible-last",true)}b.bind(a.support.touch?"touchstart":
-"click",function(){b.is(".ui-collapsible-heading-collapsed")?f.trigger("expand"):f.trigger("collapse");return false})}})})(jQuery);
-(function(a){a.fn.controlgroup=function(d){return this.each(function(){function c(e){e.removeClass("ui-btn-corner-all ui-shadow").eq(0).addClass(g[0]).end().filter(":last").addClass(g[1]).addClass("ui-controlgroup-last")}var f=a.extend({direction:a(this).data("type")||"vertical",shadow:false},d),b=a(this).find(">legend"),g=f.direction=="horizontal"?["ui-corner-left","ui-corner-right"]:["ui-corner-top","ui-corner-bottom"];a(this).find("input:eq(0)").attr("type");if(b.length){a(this).wrapInner('<div class="ui-controlgroup-controls"></div>');
-a('<div role="heading" class="ui-controlgroup-label">'+b.html()+"</div>").insertBefore(a(this).children(0));b.remove()}a(this).addClass("ui-corner-all ui-controlgroup ui-controlgroup-"+f.direction);c(a(this).find(".ui-btn"));c(a(this).find(".ui-btn-inner"));f.shadow&&a(this).addClass("ui-shadow")})}})(jQuery);(function(a){a.fn.fieldcontain=function(){return this.addClass("ui-field-contain ui-body ui-br")}})(jQuery);
-(function(a){a.widget("mobile.listview",a.mobile.widget,{options:{theme:"c",countTheme:"c",headerTheme:"b",dividerTheme:"b",splitIcon:"arrow-r",splitTheme:"b",inset:false},_create:function(){var d=this.element,c=this.options;d.addClass("ui-listview").attr("role","listbox");c.inset&&d.addClass("ui-listview-inset ui-corner-all ui-shadow");d.delegate(".ui-li","focusin",function(){a(this).attr("tabindex","0")});this._itemApply(d,d);this.refresh(true);d.keydown(function(f){var b=a(f.target),g=b.closest("li");
-switch(f.keyCode){case 38:f=g.prev();if(f.length){b.blur().attr("tabindex","-1");f.find("a").first().focus()}return false;case 40:f=g.next();if(f.length){b.blur().attr("tabindex","-1");f.find("a").first().focus()}return false;case 39:f=g.find("a.ui-li-link-alt");if(f.length){b.blur();f.first().focus()}return false;case 37:f=g.find("a.ui-link-inherit");if(f.length){b.blur();f.first().focus()}return false;case 13:case 32:b.trigger("click");return false}});d.delegate("li","click",function(f){if(!a(f.target).closest("a").length){a(this).find("a").first().trigger("click");
-return false}})},_itemApply:function(d,c){c.find(".ui-li-count").addClass("ui-btn-up-"+(d.data("counttheme")||this.options.countTheme)+" ui-btn-corner-all");c.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading");c.find("p, dl").addClass("ui-li-desc");c.find("li").find("img:eq(0)").addClass("ui-li-thumb").each(function(){a(this).closest("li").addClass(a(this).is(".ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb")});var f=c.find(".ui-li-aside");f.length&&f.each(function(b,g){a(g).prependTo(a(g).parent())});
-a.support.cssPseudoElement||a.nodeName(c[0],"ol")},_removeCorners:function(d){d.add(d.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb")).removeClass("ui-corner-top ui-corner-bottom ui-corner-br ui-corner-bl ui-corner-tr ui-corner-tl")},refresh:function(d){this._createSubPages();var c=this.options,f=this.element,b=this,g=f.data("dividertheme")||c.dividerTheme,e=f.children("li"),i=a.support.cssPseudoElement||!a.nodeName(f[0],"ol")?0:1;i&&f.find(".ui-li-dec").remove();e.attr({role:"option",tabindex:"-1"});
-e.first().attr("tabindex","0");e.each(function(h){var k=a(this),j="ui-li";if(!(!d&&k.hasClass("ui-li"))){var o=k.data("theme")||c.theme,p=k.find("a");if(p.length){var t=k.data("icon");k.buttonMarkup({wrapperEls:"div",shadow:false,corners:false,iconpos:"right",icon:p.length>1||t===false?false:t||"arrow-r",theme:o});p.first().addClass("ui-link-inherit");if(p.length>1){j+=" ui-li-has-alt";p=p.last();t=f.data("splittheme")||p.data("theme")||c.splitTheme;p.appendTo(k).attr("title",p.text()).addClass("ui-li-link-alt").empty().buttonMarkup({shadow:false,
-corners:false,theme:o,icon:false,iconpos:false}).find(".ui-btn-inner").append(a("<span>").buttonMarkup({shadow:true,corners:true,theme:t,iconpos:"notext",icon:f.data("spliticon")||p.data("icon")||c.splitIcon}))}}else if(k.data("role")==="list-divider"){j+=" ui-li-divider ui-btn ui-bar-"+g;k.attr("role","heading");if(i)i=1}else j+=" ui-li-static ui-btn-up-"+o;if(c.inset){if(h===0){j+=" ui-corner-top";k.add(k.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-tr").end().find(".ui-li-thumb").addClass("ui-corner-tl");
-k.next().next().length&&b._removeCorners(k.next())}if(h===e.length-1){j+=" ui-corner-bottom";k.add(k.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-br").end().find(".ui-li-thumb").addClass("ui-corner-bl");k.prev().prev().length&&b._removeCorners(k.prev())}}i&&j.indexOf("ui-li-divider")<0&&k.find(".ui-link-inherit").first().addClass("ui-li-jsnumbering").prepend("<span class='ui-li-dec'>"+i++ +". </span>");k.addClass(j);d||b._itemApply(f,k)}})},_idStringEscape:function(d){return d.replace(/[^a-zA-Z0-9]/g,
-"-")},_createSubPages:function(){var d=this.element,c=d.closest(".ui-page"),f=c.data("url"),b=this.options,g=this,e=c.find("[data-role='footer']").data("id");a(d.find("ul, ol").toArray().reverse()).each(function(i){var h=a(this),k=h.parent(),j=a.trim(k.contents()[0].nodeValue)||k.find("a:first").text();i=f+"&"+a.mobile.subPageUrlKey+"="+g._idStringEscape(j+" "+i);var o=h.data("theme")||b.theme,p=h.data("counttheme")||d.data("counttheme")||b.countTheme;h.wrap("<div data-role='page'><div data-role='content'></div></div>").parent().before("<div data-role='header' data-theme='"+
-b.headerTheme+"'><div class='ui-title'>"+j+"</div></div>").after(e?a("<div>",{"data-role":"footer","data-id":e,"class":"ui-footer-duplicate"}):"").parent().attr({"data-url":i,"data-theme":o,"data-count-theme":p}).appendTo(a.mobile.pageContainer).page();h=k.find("a:first");h.length||(h=a("<a></a>").html(j).prependTo(k.empty()));h.attr("href","#"+i)}).listview()}})})(jQuery);
-(function(a){a.mobile.listview.prototype.options.filter=false;a("[data-role='listview']").live("listviewcreate",function(){var d=a(this);if(d.data("listview").options.filter){var c=a("<form>",{"class":"ui-listview-filter ui-bar-c",role:"search"});a("<input>",{placeholder:"Filter results...","data-type":"search"}).bind("keyup change",function(){var f=this.value.toLowerCase();d.children().show();f&&d.children().filter(function(){return a(this).text().toLowerCase().indexOf(f)===-1}).hide()}).appendTo(c).textinput();
-c.insertBefore(d)}})})(jQuery);
-(function(a){a.widget("mobile.dialog",a.mobile.widget,{options:{},_create:function(){this.element.attr("role","dialog").addClass("ui-page ui-dialog ui-body-a").find("[data-role=header]").addClass("ui-corner-top ui-overlay-shadow").prepend('<a href="#" data-icon="delete" data-rel="back" data-iconpos="notext">Close</a>').end().find('.ui-content:not([class*="ui-body-"])').addClass("ui-body-c").end().find(".ui-content,[data-role=footer]").last().addClass("ui-corner-bottom ui-overlay-shadow");this.element.bind("click submit",
-function(d){d=d.type=="click"?a(d.target).closest("a"):a(d.target).closest("form");d.length&&!d.data("transition")&&d.attr("data-transition",a.mobile.urlHistory.getActive().transition).attr("data-direction","reverse")})},close:function(){window.history.back()}})})(jQuery);
-(function(a,d){a.widget("mobile.navbar",a.mobile.widget,{options:{iconpos:"top",grid:null},_create:function(){var c=this.element,f=c.find("a"),b=f.filter("[data-icon]").length?this.options.iconpos:d;c.addClass("ui-navbar").attr("role","navigation").find("ul").grid({grid:this.options.grid});b||c.addClass("ui-navbar-noicons");f.buttonMarkup({corners:false,shadow:false,iconpos:b});c.delegate("a","click",function(){f.removeClass("ui-btn-active");a(this).addClass("ui-btn-active")})}})})(jQuery);
-(function(a){a.fn.grid=function(d){return this.each(function(){var c=a.extend({grid:null},d),f=a(this).children(),b={a:2,b:3,c:4,d:5};c=c.grid;if(!c)if(f.length<=5)for(var g in b){if(b[g]==f.length)c=g}else c="a";b=b[c];a(this).addClass("ui-grid-"+c);f.filter(":nth-child("+b+"n+1)").addClass("ui-block-a");f.filter(":nth-child("+b+"n+2)").addClass("ui-block-b");b>2&&f.filter(":nth-child(3n+3)").addClass("ui-block-c");b>3&&f.filter(":nth-child(4n+4)").addClass("ui-block-d");b>4&&f.filter(":nth-child(5n+5)").addClass("ui-block-e")})}})(jQuery);
 

--- /dev/null
+++ b/js/jquery.mobile-1.0a4.js
@@ -1,1 +1,5113 @@
-
+/*!
+ * jQuery Mobile v1.0a4
+ * http://jquerymobile.com/
+ *
+ * Copyright 2010, jQuery Project
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ */
+/*!
+ * jQuery UI Widget @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Widget
+ */
+(function( $, undefined ) {
+
+// jQuery 1.4+
+if ( $.cleanData ) {
+	var _cleanData = $.cleanData;
+	$.cleanData = function( elems ) {
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			$( elem ).triggerHandler( "remove" );
+		}
+		_cleanData( elems );
+	};
+} else {
+	var _remove = $.fn.remove;
+	$.fn.remove = function( selector, keepData ) {
+		return this.each(function() {
+			if ( !keepData ) {
+				if ( !selector || $.filter( selector, [ this ] ).length ) {
+					$( "*", this ).add( [ this ] ).each(function() {
+						$( this ).triggerHandler( "remove" );
+					});
+				}
+			}
+			return _remove.call( $(this), selector, keepData );
+		});
+	};
+}
+
+$.widget = function( name, base, prototype ) {
+	var namespace = name.split( "." )[ 0 ],
+		fullName;
+	name = name.split( "." )[ 1 ];
+	fullName = namespace + "-" + name;
+
+	if ( !prototype ) {
+		prototype = base;
+		base = $.Widget;
+	}
+
+	// create selector for plugin
+	$.expr[ ":" ][ fullName ] = function( elem ) {
+		return !!$.data( elem, name );
+	};
+
+	$[ namespace ] = $[ namespace ] || {};
+	$[ namespace ][ name ] = function( options, element ) {
+		// allow instantiation without initializing for simple inheritance
+		if ( arguments.length ) {
+			this._createWidget( options, element );
+		}
+	};
+
+	var basePrototype = new base();
+	// we need to make the options hash a property directly on the new instance
+	// otherwise we'll modify the options hash on the prototype that we're
+	// inheriting from
+//	$.each( basePrototype, function( key, val ) {
+//		if ( $.isPlainObject(val) ) {
+//			basePrototype[ key ] = $.extend( {}, val );
+//		}
+//	});
+	basePrototype.options = $.extend( true, {}, basePrototype.options );
+	$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
+		namespace: namespace,
+		widgetName: name,
+		widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
+		widgetBaseClass: fullName
+	}, prototype );
+
+	$.widget.bridge( name, $[ namespace ][ name ] );
+};
+
+$.widget.bridge = function( name, object ) {
+	$.fn[ name ] = function( options ) {
+		var isMethodCall = typeof options === "string",
+			args = Array.prototype.slice.call( arguments, 1 ),
+			returnValue = this;
+
+		// allow multiple hashes to be passed on init
+		options = !isMethodCall && args.length ?
+			$.extend.apply( null, [ true, options ].concat(args) ) :
+			options;
+
+		// prevent calls to internal methods
+		if ( isMethodCall && options.charAt( 0 ) === "_" ) {
+			return returnValue;
+		}
+
+		if ( isMethodCall ) {
+			this.each(function() {
+				var instance = $.data( this, name );
+				if ( !instance ) {
+					throw "cannot call methods on " + name + " prior to initialization; " +
+						"attempted to call method '" + options + "'";
+				}
+				if ( !$.isFunction( instance[options] ) ) {
+					throw "no such method '" + options + "' for " + name + " widget instance";
+				}
+				var methodValue = instance[ options ].apply( instance, args );
+				if ( methodValue !== instance && methodValue !== undefined ) {
+					returnValue = methodValue;
+					return false;
+				}
+			});
+		} else {
+			this.each(function() {
+				var instance = $.data( this, name );
+				if ( instance ) {
+					instance.option( options || {} )._init();
+				} else {
+					$.data( this, name, new object( options, this ) );
+				}
+			});
+		}
+
+		return returnValue;
+	};
+};
+
+$.Widget = function( options, element ) {
+	// allow instantiation without initializing for simple inheritance
+	if ( arguments.length ) {
+		this._createWidget( options, element );
+	}
+};
+
+$.Widget.prototype = {
+	widgetName: "widget",
+	widgetEventPrefix: "",
+	options: {
+		disabled: false
+	},
+	_createWidget: function( options, element ) {
+		// $.widget.bridge stores the plugin instance, but we do it anyway
+		// so that it's stored even before the _create function runs
+		$.data( element, this.widgetName, this );
+		this.element = $( element );
+		this.options = $.extend( true, {},
+			this.options,
+			this._getCreateOptions(),
+			options );
+
+		var self = this;
+		this.element.bind( "remove." + this.widgetName, function() {
+			self.destroy();
+		});
+
+		this._create();
+		this._trigger( "create" );
+		this._init();
+	},
+	_getCreateOptions: function() {
+		var options = {};
+		if ( $.metadata ) {
+			options = $.metadata.get( element )[ this.widgetName ];
+		}
+		return options;
+	},
+	_create: function() {},
+	_init: function() {},
+
+	destroy: function() {
+		this.element
+			.unbind( "." + this.widgetName )
+			.removeData( this.widgetName );
+		this.widget()
+			.unbind( "." + this.widgetName )
+			.removeAttr( "aria-disabled" )
+			.removeClass(
+				this.widgetBaseClass + "-disabled " +
+				"ui-state-disabled" );
+	},
+
+	widget: function() {
+		return this.element;
+	},
+
+	option: function( key, value ) {
+		var options = key;
+
+		if ( arguments.length === 0 ) {
+			// don't return a reference to the internal hash
+			return $.extend( {}, this.options );
+		}
+
+		if  (typeof key === "string" ) {
+			if ( value === undefined ) {
+				return this.options[ key ];
+			}
+			options = {};
+			options[ key ] = value;
+		}
+
+		this._setOptions( options );
+
+		return this;
+	},
+	_setOptions: function( options ) {
+		var self = this;
+		$.each( options, function( key, value ) {
+			self._setOption( key, value );
+		});
+
+		return this;
+	},
+	_setOption: function( key, value ) {
+		this.options[ key ] = value;
+
+		if ( key === "disabled" ) {
+			this.widget()
+				[ value ? "addClass" : "removeClass"](
+					this.widgetBaseClass + "-disabled" + " " +
+					"ui-state-disabled" )
+				.attr( "aria-disabled", value );
+		}
+
+		return this;
+	},
+
+	enable: function() {
+		return this._setOption( "disabled", false );
+	},
+	disable: function() {
+		return this._setOption( "disabled", true );
+	},
+
+	_trigger: function( type, event, data ) {
+		var callback = this.options[ type ];
+
+		event = $.Event( event );
+		event.type = ( type === this.widgetEventPrefix ?
+			type :
+			this.widgetEventPrefix + type ).toLowerCase();
+		data = data || {};
+
+		// copy original event properties over to the new event
+		// this would happen if we could call $.event.fix instead of $.Event
+		// but we don't have a way to force an event to be fixed multiple times
+		if ( event.originalEvent ) {
+			for ( var i = $.event.props.length, prop; i; ) {
+				prop = $.event.props[ --i ];
+				event[ prop ] = event.originalEvent[ prop ];
+			}
+		}
+
+		this.element.trigger( event, data );
+
+		return !( $.isFunction(callback) &&
+			callback.call( this.element[0], event, data ) === false ||
+			event.isDefaultPrevented() );
+	}
+};
+
+})( jQuery );
+/*
+* jQuery Mobile Framework : widget factory extentions for mobile
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+
+$.widget( "mobile.widget", {
+	_getCreateOptions: function() {
+		var elem = this.element,
+			options = {};
+		$.each( this.options, function( option ) {
+			var value = elem.jqmData( option.replace( /[A-Z]/g, function( c ) {
+				return "-" + c.toLowerCase();
+			} ) );
+			if ( value !== undefined ) {
+				options[ option ] = value;
+			}
+		});
+		return options;
+	}
+});
+
+})( jQuery );
+/*
+* jQuery Mobile Framework : resolution and CSS media query related helpers and behavior
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+
+var $window = $(window),
+	$html = $( "html" ),
+
+	//media-query-like width breakpoints, which are translated to classes on the html element
+	resolutionBreakpoints = [320,480,768,1024];
+
+
+/* $.mobile.media method: pass a CSS media type or query and get a bool return
+	note: this feature relies on actual media query support for media queries, though types will work most anywhere
+	examples:
+		$.mobile.media('screen') //>> tests for screen media type
+		$.mobile.media('screen and (min-width: 480px)') //>> tests for screen media type with window width > 480px
+		$.mobile.media('@media screen and (-webkit-min-device-pixel-ratio: 2)') //>> tests for webkit 2x pixel ratio (iPhone 4)
+*/
+$.mobile.media = (function() {
+	// TODO: use window.matchMedia once at least one UA implements it
+	var cache = {},
+		testDiv = $( "<div id='jquery-mediatest'>" ),
+		fakeBody = $( "<body>" ).append( testDiv );
+
+	return function( query ) {
+		if ( !( query in cache ) ) {
+			var styleBlock = document.createElement('style'),
+        		cssrule = "@media " + query + " { #jquery-mediatest { position:absolute; } }";
+	        //must set type for IE!	
+	        styleBlock.type = "text/css";
+	        if (styleBlock.styleSheet){ 
+	          styleBlock.styleSheet.cssText = cssrule;
+	        } 
+	        else {
+	          styleBlock.appendChild(document.createTextNode(cssrule));
+	        } 
+				
+			$html.prepend( fakeBody ).prepend( styleBlock );
+			cache[ query ] = testDiv.css( "position" ) === "absolute";
+			fakeBody.add( styleBlock ).remove();
+		}
+		return cache[ query ];
+	};
+})();
+
+/*
+	private function for adding/removing breakpoint classes to HTML element for faux media-query support
+	It does not require media query support, instead using JS to detect screen width > cross-browser support
+	This function is called on orientationchange, resize, and mobileinit, and is bound via the 'htmlclass' event namespace
+*/
+function detectResolutionBreakpoints(){
+	var currWidth = $window.width(),
+		minPrefix = "min-width-",
+		maxPrefix = "max-width-",
+		minBreakpoints = [],
+		maxBreakpoints = [],
+		unit = "px",
+		breakpointClasses;
+
+	$html.removeClass( minPrefix + resolutionBreakpoints.join(unit + " " + minPrefix) + unit + " " +
+		maxPrefix + resolutionBreakpoints.join( unit + " " + maxPrefix) + unit );
+
+	$.each(resolutionBreakpoints,function( i, breakPoint ){
+		if( currWidth >= breakPoint ){
+			minBreakpoints.push( minPrefix + breakPoint + unit );
+		}
+		if( currWidth <= breakPoint ){
+			maxBreakpoints.push( maxPrefix + breakPoint + unit );
+		}
+	});
+
+	if( minBreakpoints.length ){ breakpointClasses = minBreakpoints.join(" "); }
+	if( maxBreakpoints.length ){ breakpointClasses += " " +  maxBreakpoints.join(" "); }
+
+	$html.addClass( breakpointClasses );
+};
+
+/* $.mobile.addResolutionBreakpoints method:
+	pass either a number or an array of numbers and they'll be added to the min/max breakpoint classes
+	Examples:
+		$.mobile.addResolutionBreakpoints( 500 );
+		$.mobile.addResolutionBreakpoints( [500, 1200] );
+*/
+$.mobile.addResolutionBreakpoints = function( newbps ){
+	if( $.type( newbps ) === "array" ){
+		resolutionBreakpoints = resolutionBreakpoints.concat( newbps );
+	}
+	else {
+		resolutionBreakpoints.push( newbps );
+	}
+	resolutionBreakpoints.sort(function(a,b){ return a-b; });
+	detectResolutionBreakpoints();
+};
+
+/* 	on mobileinit, add classes to HTML element
+	and set handlers to update those on orientationchange and resize*/
+$(document).bind("mobileinit.htmlclass", function(){
+	/* bind to orientationchange and resize
+	to add classes to HTML element for min/max breakpoints and orientation */
+	$window.bind("orientationchange.htmlclass resize.htmlclass", function(event){
+		//add orientation class to HTML element on flip/resize.
+		if(event.orientation){
+			$html.removeClass( "portrait landscape" ).addClass( event.orientation );
+		}
+		//add classes to HTML element for min/max breakpoints
+		detectResolutionBreakpoints();
+	});
+});
+
+/* Manually trigger an orientationchange event when the dom ready event fires.
+   This will ensure that any viewport meta tag that may have been injected
+   has taken effect already, allowing us to properly calculate the width of the
+   document.
+*/
+$(function(){
+	//trigger event manually
+	$window.trigger( "orientationchange.htmlclass" );
+});
+
+})(jQuery);/*
+* jQuery Mobile Framework : support tests
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* Note: Code is in draft form and is subject to change 
+*/
+(function($, undefined ) {
+
+
+
+var fakeBody = $( "<body>" ).prependTo( "html" ),
+	fbCSS = fakeBody[0].style,
+	vendors = ['webkit','moz','o'],
+	webos = window.palmGetResource || window.PalmServiceBridge, //only used to rule out scrollTop 
+	bb = window.blackberry; //only used to rule out box shadow, as it's filled opaque on BB
+
+//thx Modernizr
+function propExists( prop ){
+	var uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1),
+		props   = (prop + ' ' + vendors.join(uc_prop + ' ') + uc_prop).split(' ');
+	for(var v in props){
+		if( fbCSS[ v ] !== undefined ){
+			return true;
+		}
+	}
+};
+
+//test for dynamic-updating base tag support (allows us to avoid href,src attr rewriting)
+function baseTagTest(){
+	var fauxBase = location.protocol + '//' + location.host + location.pathname + "ui-dir/",
+		base = $("head base"),
+		fauxEle = null,
+		href = '';
+	if (!base.length) {
+		base = fauxEle = $("<base>", {"href": fauxBase}).appendTo("head");
+	}
+	else {
+		href = base.attr("href");
+	}
+	var link = $( "<a href='testurl'></a>" ).prependTo( fakeBody ),
+		rebase = link[0].href;
+	base[0].href = href ? href : location.pathname;
+	if (fauxEle) {
+		fauxEle.remove();
+	}
+	return rebase.indexOf(fauxBase) === 0;
+};
+
+
+//non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683
+//allows for inclusion of IE 6+, including Windows Mobile 7
+$.mobile.browser = {};
+$.mobile.browser.ie = (function() {
+    var v = 3, div = document.createElement('div'), a = div.all || [];
+    while (div.innerHTML = '<!--[if gt IE '+(++v)+']><br><![endif]-->', a[0]); 
+    return v > 4 ? v : !v;
+}());
+
+$.extend( $.support, {
+	orientation: "orientation" in window,
+	touch: "ontouchend" in document,
+	cssTransitions: "WebKitTransitionEvent" in window,
+	pushState: !!history.pushState,
+	mediaquery: $.mobile.media('only all'),
+	cssPseudoElement: !!propExists('content'),
+	boxShadow: !!propExists('boxShadow') && !bb,
+	scrollTop: ("pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[0]) && !webos,
+	dynamicBaseTag: baseTagTest(),
+	eventCapture: ("addEventListener" in document) // This is a weak test. We may want to beef this up later.
+});
+
+fakeBody.remove();
+
+//for ruling out shadows via css
+if( !$.support.boxShadow ){ $('html').addClass('ui-mobile-nosupport-boxshadow'); }
+
+})( jQuery );/*
+* jQuery Mobile Framework : "mouse" plugin
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+
+// This plugin is an experiment for abstracting away the touch and mouse
+// events so that developers don't have to worry about which method of input
+// the device their document is loaded on supports.
+//
+// The idea here is to allow the developer to register listeners for the
+// basic mouse events, such as mousedown, mousemove, mouseup, and click,
+// and the plugin will take care of registering the correct listeners
+// behind the scenes to invoke the listener at the fastest possible time
+// for that device, while still retaining the order of event firing in
+// the traditional mouse environment, should multiple handlers be registered
+// on the same element for different events.
+//
+// The current version exposes the following virtual events to jQuery bind methods:
+// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
+
+(function($, window, document, undefined) {
+
+var dataPropertyName = "virtualMouseBindings",
+	touchTargetPropertyName = "virtualTouchID",
+	virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),
+	touchEventProps = "clientX clientY pageX pageY screenX screenY".split(" "),
+	activeDocHandlers = {},
+	resetTimerID = 0,
+	startX = 0,
+	startY = 0,
+	startScrollX = 0,
+	startScrollY = 0,
+	didScroll = false,
+	clickBlockList = [],
+	blockMouseTriggers = false,
+	scrollTopSupported = $.support.scrollTop,
+	eventCaptureSupported = $.support.eventCapture,
+	$document = $(document),
+	nextTouchID = 1,
+	lastTouchID = 0;
+
+$.vmouse = {
+	moveDistanceThreshold: 10,
+	clickDistanceThreshold: 10,
+	resetTimerDuration: 1500
+};
+
+function getNativeEvent(event)
+{
+	while (event && typeof event.originalEvent !== "undefined") {
+		event = event.originalEvent;
+	}
+	return event;
+}
+
+function createVirtualEvent(event, eventType)
+{
+	var t = event.type;
+	event = $.Event(event);
+	event.type = eventType;
+	
+	var oe = event.originalEvent;
+	var props = $.event.props;
+	
+	// copy original event properties over to the new event
+	// this would happen if we could call $.event.fix instead of $.Event
+	// but we don't have a way to force an event to be fixed multiple times
+	if (oe) {
+		for ( var i = props.length, prop; i; ) {
+			prop = props[ --i ];
+			event[prop] = oe[prop];
+		}
+	}
+	
+	if (t.search(/^touch/) !== -1){
+		var ne = getNativeEvent(oe);
+		if (typeof ne.touches !== "undefined" && ne.touches[0]){
+			var touch = ne.touches[0];
+			for (var i = 0; i < touchEventProps.length; i++){
+				var prop = touchEventProps[i];
+				event[prop] = touch[prop];
+			}
+		}
+	}
+
+	return event;
+}
+
+function getVirtualBindingFlags(element)
+{
+	var flags = {};
+	var $ele = $(element);
+	while ($ele && $ele.length){
+		var b = $ele.data(dataPropertyName);
+		for (var k in b) {
+			if (b[k]){
+				flags[k] = flags.hasVirtualBinding = true;
+			}
+		}
+		$ele = $ele.parent();
+	}
+	return flags;
+}
+
+function getClosestElementWithVirtualBinding(element, eventType)
+{
+	var $ele = $(element);
+	while ($ele && $ele.length){
+		var b = $ele.data(dataPropertyName);
+		if (b && (!eventType || b[eventType])) {
+			return $ele;
+		}
+		$ele = $ele.parent();
+	}
+	return null;
+}
+
+function enableTouchBindings()
+{
+	if (!activeDocHandlers["touchbindings"]){
+		$document.bind("touchend", handleTouchEnd)
+		
+			// On touch platforms, touching the screen and then dragging your finger
+			// causes the window content to scroll after some distance threshold is
+			// exceeded. On these platforms, a scroll prevents a click event from being
+			// dispatched, and on some platforms, even the touchend is suppressed. To
+			// mimic the suppression of the click event, we need to watch for a scroll
+			// event. Unfortunately, some platforms like iOS don't dispatch scroll
+			// events until *AFTER* the user lifts their finger (touchend). This means
+			// we need to watch both scroll and touchmove events to figure out whether
+			// or not a scroll happenens before the touchend event is fired.
+		
+			.bind("touchmove", handleTouchMove)
+			.bind("scroll", handleScroll);
+
+		activeDocHandlers["touchbindings"] = 1;
+	}
+}
+
+function disableTouchBindings()
+{
+	if (activeDocHandlers["touchbindings"]){
+		$document.unbind("touchmove", handleTouchMove)
+			.unbind("touchend", handleTouchEnd)
+			.unbind("scroll", handleScroll);
+		activeDocHandlers["touchbindings"] = 0;
+	}
+}
+
+function enableMouseBindings()
+{
+	lastTouchID = 0;
+	clickBlockList.length = 0;
+	blockMouseTriggers = false;
+
+	// When mouse bindings are enabled, our
+	// touch bindings are disabled.
+	disableTouchBindings();
+}
+
+function disableMouseBindings()
+{
+	// When mouse bindings are disabled, our
+	// touch bindings are enabled.
+	enableTouchBindings();
+}
+
+function startResetTimer()
+{
+	clearResetTimer();
+	resetTimerID = setTimeout(function(){
+		resetTimerID = 0;
+		enableMouseBindings();
+	}, $.vmouse.resetTimerDuration);
+}
+
+function clearResetTimer()
+{
+	if (resetTimerID){
+		clearTimeout(resetTimerID);
+		resetTimerID = 0;
+	}
+}
+
+function triggerVirtualEvent(eventType, event, flags)
+{
+	var defaultPrevented = false;
+
+	if ((flags && flags[eventType]) || (!flags && getClosestElementWithVirtualBinding(event.target, eventType))) {
+		var ve = createVirtualEvent(event, eventType);
+		$(event.target).trigger(ve);
+		defaultPrevented = ve.isDefaultPrevented();
+	}
+
+	return defaultPrevented;
+}
+
+function mouseEventCallback(event)
+{
+	var touchID = $(event.target).data(touchTargetPropertyName);
+	if (!blockMouseTriggers && (!lastTouchID || lastTouchID !== touchID)){
+		triggerVirtualEvent("v" + event.type, event);
+	}
+}
+
+function handleTouchStart(event)
+{
+	var touches = getNativeEvent(event).touches;
+	if (touches && touches.length === 1){
+		var target = event.target,
+			flags = getVirtualBindingFlags(target);
+	
+		if (flags.hasVirtualBinding){
+			lastTouchID = nextTouchID++;
+			$(target).data(touchTargetPropertyName, lastTouchID);
+	
+			clearResetTimer();
+			
+			disableMouseBindings();
+			didScroll = false;
+			
+			var t = getNativeEvent(event).touches[0];
+			startX = t.pageX;
+			startY = t.pageY;
+		
+			if (scrollTopSupported){
+				startScrollX = window.pageXOffset;
+				startScrollY = window.pageYOffset;
+			}
+		
+			triggerVirtualEvent("vmouseover", event, flags);
+			triggerVirtualEvent("vmousedown", event, flags);
+		}
+	}
+}
+
+function handleScroll(event)
+{
+	if (!didScroll){
+		triggerVirtualEvent("vmousecancel", event, getVirtualBindingFlags(event.target));
+	}
+
+	didScroll = true;
+	startResetTimer();
+}
+
+function handleTouchMove(event)
+{
+	var t = getNativeEvent(event).touches[0];
+
+	var didCancel = didScroll,
+		moveThreshold = $.vmouse.moveDistanceThreshold;
+	didScroll = didScroll
+		|| (scrollTopSupported && (startScrollX !== window.pageXOffset || startScrollY !== window.pageYOffset))
+		|| (Math.abs(t.pageX - startX) > moveThreshold || Math.abs(t.pageY - startY) > moveThreshold);
+
+	var flags = getVirtualBindingFlags(event.target);
+	if (didScroll && !didCancel){
+		triggerVirtualEvent("vmousecancel", event, flags);
+	}
+	triggerVirtualEvent("vmousemove", event, flags);
+	startResetTimer();
+}
+
+function handleTouchEnd(event)
+{
+	disableTouchBindings();
+
+	var flags = getVirtualBindingFlags(event.target);
+	triggerVirtualEvent("vmouseup", event, flags);
+	if (!didScroll){
+		if (triggerVirtualEvent("vclick", event, flags)){
+			// The target of the mouse events that follow the touchend
+			// event don't necessarily match the target used during the
+			// touch. This means we need to rely on coordinates for blocking
+			// any click that is generated.
+			var t = getNativeEvent(event).changedTouches[0];
+			clickBlockList.push({ touchID: lastTouchID, x: t.clientX, y: t.clientY });
+
+			// Prevent any mouse events that follow from triggering
+			// virtual event notifications.
+			blockMouseTriggers = true;
+		}
+	}
+	triggerVirtualEvent("vmouseout", event, flags);
+	didScroll = false;
+	
+	startResetTimer();
+}
+
+function hasVirtualBindings($ele)
+{
+	var bindings = $ele.data(dataPropertyName), k;
+	if (bindings){
+		for (k in bindings){
+			if (bindings[k]){
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+function dummyMouseHandler(){}
+
+function getSpecialEventObject(eventType)
+{
+	var realType = eventType.substr(1);
+	return {
+		setup: function(data, namespace) {
+			// If this is the first virtual mouse binding for this element,
+			// add a bindings object to its data.
+
+			var $this = $(this);
+
+			if (!hasVirtualBindings($this)){
+				$this.data(dataPropertyName, {});
+			}
+
+			// If setup is called, we know it is the first binding for this
+			// eventType, so initialize the count for the eventType to zero.
+
+			var bindings = $this.data(dataPropertyName);
+			bindings[eventType] = true;
+
+			// If this is the first virtual mouse event for this type,
+			// register a global handler on the document.
+
+			activeDocHandlers[eventType] = (activeDocHandlers[eventType] || 0) + 1;
+			if (activeDocHandlers[eventType] === 1){
+				$document.bind(realType, mouseEventCallback);
+			}
+
+			// Some browsers, like Opera Mini, won't dispatch mouse/click events
+			// for elements unless they actually have handlers registered on them.
+			// To get around this, we register dummy handlers on the elements.
+
+			$this.bind(realType, dummyMouseHandler);
+
+			// For now, if event capture is not supported, we rely on mouse handlers.
+			if (eventCaptureSupported){
+				// If this is the first virtual mouse binding for the document,
+				// register our touchstart handler on the document.
+	
+				activeDocHandlers["touchstart"] = (activeDocHandlers["touchstart"] || 0) + 1;
+				if (activeDocHandlers["touchstart"] === 1) {
+					$document.bind("touchstart", handleTouchStart);
+				}
+			}
+		},
+
+		teardown: function(data, namespace) {
+			// If this is the last virtual binding for this eventType,
+			// remove its global handler from the document.
+
+			--activeDocHandlers[eventType];
+			if (!activeDocHandlers[eventType]){
+				$document.unbind(realType, mouseEventCallback);
+			}
+
+			if (eventCaptureSupported){
+				// If this is the last virtual mouse binding in existence,
+				// remove our document touchstart listener.
+	
+				--activeDocHandlers["touchstart"];
+				if (!activeDocHandlers["touchstart"]) {
+					$document.unbind("touchstart", handleTouchStart);
+				}
+			}
+
+			var $this = $(this),
+				bindings = $this.data(dataPropertyName);
+			bindings[eventType] = false;
+
+			// Unregister the dummy event handler.
+
+			$this.unbind(realType, dummyMouseHandler);
+
+			// If this is the last virtual mouse binding on the
+			// element, remove the binding data from the element.
+
+			if (!hasVirtualBindings($this)){
+				$this.removeData(dataPropertyName);
+			}
+		}
+	};
+}
+
+// Expose our custom events to the jQuery bind/unbind mechanism.
+
+for (var i = 0; i < virtualEventNames.length; i++){
+	$.event.special[virtualEventNames[i]] = getSpecialEventObject(virtualEventNames[i]);
+}
+
+// Add a capture click handler to block clicks.
+// Note that we require event capture support for this so if the device
+// doesn't support it, we punt for now and rely solely on mouse events.
+if (eventCaptureSupported){
+	document.addEventListener("click", function(e){
+		var cnt = clickBlockList.length;
+		var target = e.target;
+		if (cnt) {
+			var x = e.clientX,
+				y = e.clientY,
+				threshold = $.vmouse.clickDistanceThreshold;
+
+			// The idea here is to run through the clickBlockList to see if
+			// the current click event is in the proximity of one of our
+			// vclick events that had preventDefault() called on it. If we find
+			// one, then we block the click.
+			//
+			// Why do we have to rely on proximity?
+			//
+			// Because the target of the touch event that triggered the vclick
+			// can be different from the target of the click event synthesized
+			// by the browser. The target of a mouse/click event that is syntehsized
+			// from a touch event seems to be implementation specific. For example,
+			// some browsers will fire mouse/click events for a link that is near
+			// a touch event, even though the target of the touchstart/touchend event
+			// says the user touched outside the link. Also, it seems that with most
+			// browsers, the target of the mouse/click event is not calculated until the
+			// time it is dispatched, so if you replace an element that you touched
+			// with another element, the target of the mouse/click will be the new
+			// element underneath that point.
+			//
+			// Aside from proximity, we also check to see if the target and any
+			// of its ancestors were the ones that blocked a click. This is necessary
+			// because of the strange mouse/click target calculation done in the
+			// Android 2.1 browser, where if you click on an element, and there is a
+			// mouse/click handler on one of its ancestors, the target will be the
+			// innermost child of the touched element, even if that child is no where
+			// near the point of touch.
+			
+			var ele = target;
+			while (ele) {
+				for (var i = 0; i < cnt; i++) {
+					var o = clickBlockList[i],
+						touchID = 0;
+					if ((ele === target && Math.abs(o.x - x) < threshold && Math.abs(o.y - y) < threshold) || $(ele).data(touchTargetPropertyName) === o.touchID){
+						// XXX: We may want to consider removing matches from the block list
+						//      instead of waiting for the reset timer to fire.
+						e.preventDefault();
+						e.stopPropagation();
+						return;
+					}
+				}
+				ele = ele.parentNode;
+			}
+		}
+	}, true);
+}
+})(jQuery, window, document);/*
+* jQuery Mobile Framework : events
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+
+// add new event shortcuts
+$.each( "touchstart touchmove touchend orientationchange tap taphold swipe swipeleft swiperight scrollstart scrollstop".split( " " ), function( i, name ) {
+	$.fn[ name ] = function( fn ) {
+		return fn ? this.bind( name, fn ) : this.trigger( name );
+	};
+	$.attrFn[ name ] = true;
+});
+
+var supportTouch = $.support.touch,
+	scrollEvent = "touchmove scroll",
+	touchStartEvent = supportTouch ? "touchstart" : "mousedown",
+	touchStopEvent = supportTouch ? "touchend" : "mouseup",
+	touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
+
+function triggerCustomEvent(obj, eventType, event)
+{
+	var originalType = event.type;
+	event.type = eventType;
+	$.event.handle.call( obj, event );
+	event.type = originalType;
+}
+
+// also handles scrollstop
+$.event.special.scrollstart = {
+	enabled: true,
+	
+	setup: function() {
+		var thisObject = this,
+			$this = $( thisObject ),
+			scrolling,
+			timer;
+		
+		function trigger( event, state ) {
+			scrolling = state;
+			triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
+		}
+		
+		// iPhone triggers scroll after a small delay; use touchmove instead
+		$this.bind( scrollEvent, function( event ) {
+			if ( !$.event.special.scrollstart.enabled ) {
+				return;
+			}
+			
+			if ( !scrolling ) {
+				trigger( event, true );
+			}
+			
+			clearTimeout( timer );
+			timer = setTimeout(function() {
+				trigger( event, false );
+			}, 50 );
+		});
+	}
+};
+
+// also handles taphold
+$.event.special.tap = {
+	setup: function() {
+		var thisObject = this,
+			$this = $( thisObject );
+		
+		$this
+			.bind("vmousedown", function( event ) {
+				if ( event.which && event.which !== 1 ) {
+					return false;
+				}
+				
+				var touching = true,
+					origTarget = event.target,
+					origEvent = event.originalEvent,
+					timer;
+					
+				function clearTapHandlers() {
+					touching = false;
+					clearTimeout(timer);
+					$(this).unbind("vmouseclick", clickHandler).unbind("vmousecancel", clearTapHandlers);
+				}
+				
+				function clickHandler(event) {
+					clearTapHandlers();
+
+					/* ONLY trigger a 'tap' event if the start target is
+					 * the same as the stop target.
+					 */
+					if ( origTarget == event.target ) {
+						triggerCustomEvent( thisObject, "tap", event );
+					}
+				}
+
+				$this.bind("vmousecancel", clearTapHandlers).bind("vclick", clickHandler);
+
+				timer = setTimeout(function() {
+					if ( touching ) {
+						triggerCustomEvent( thisObject, "taphold", event );
+					}
+				}, 750 );
+			});
+	}
+};
+
+// also handles swipeleft, swiperight
+$.event.special.swipe = {
+	setup: function() {
+		var thisObject = this,
+			$this = $( thisObject );
+		
+		$this
+			.bind( touchStartEvent, function( event ) {
+				var data = event.originalEvent.touches ?
+						event.originalEvent.touches[ 0 ] :
+						event,
+					start = {
+						time: (new Date).getTime(),
+						coords: [ data.pageX, data.pageY ],
+						origin: $( event.target )
+					},
+					stop;
+				
+				function moveHandler( event ) {
+					if ( !start ) {
+						return;
+					}
+					
+					var data = event.originalEvent.touches ?
+							event.originalEvent.touches[ 0 ] :
+							event;
+					stop = {
+							time: (new Date).getTime(),
+							coords: [ data.pageX, data.pageY ]
+					};
+					
+					// prevent scrolling
+					if ( Math.abs( start.coords[0] - stop.coords[0] ) > 10 ) {
+						event.preventDefault();
+					}
+				}
+				
+				$this
+					.bind( touchMoveEvent, moveHandler )
+					.one( touchStopEvent, function( event ) {
+						$this.unbind( touchMoveEvent, moveHandler );
+						if ( start && stop ) {
+							if ( stop.time - start.time < 1000 && 
+									Math.abs( start.coords[0] - stop.coords[0]) > 30 &&
+									Math.abs( start.coords[1] - stop.coords[1]) < 75 ) {
+								start.origin
+								.trigger( "swipe" )
+
+								.trigger( start.coords[0] > stop.coords[0] ? "swipeleft" : "swiperight" );
+							}
+						}
+						start = stop = undefined;
+					});
+			});
+	}
+};
+
+(function($){
+	// "Cowboy" Ben Alman
+	
+	var win = $(window),
+		special_event,
+		get_orientation,
+		last_orientation;
+	
+	$.event.special.orientationchange = special_event = {
+		setup: function(){
+			// If the event is supported natively, return false so that jQuery
+			// will bind to the event using DOM methods.
+			if ( $.support.orientation ) { return false; }
+			
+			// Get the current orientation to avoid initial double-triggering.
+			last_orientation = get_orientation();
+			
+			// Because the orientationchange event doesn't exist, simulate the
+			// event by testing window dimensions on resize.
+			win.bind( "resize", handler );
+		},
+		teardown: function(){
+			// If the event is not supported natively, return false so that
+			// jQuery will unbind the event using DOM methods.
+			if ( $.support.orientation ) { return false; }
+			
+			// Because the orientationchange event doesn't exist, unbind the
+			// resize event handler.
+			win.unbind( "resize", handler );
+		},
+		add: function( handleObj ) {
+			// Save a reference to the bound event handler.
+			var old_handler = handleObj.handler;
+			
+			handleObj.handler = function( event ) {
+				// Modify event object, adding the .orientation property.
+				event.orientation = get_orientation();
+				
+				// Call the originally-bound event handler and return its result.
+				return old_handler.apply( this, arguments );
+			};
+		}
+	};
+	
+	// If the event is not supported natively, this handler will be bound to
+	// the window resize event to simulate the orientationchange event.
+	function handler() {
+		// Get the current orientation.
+		var orientation = get_orientation();
+		
+		if ( orientation !== last_orientation ) {
+			// The orientation has changed, so trigger the orientationchange event.
+			last_orientation = orientation;
+			win.trigger( "orientationchange" );
+		}
+	};
+	
+	// Get the current page orientation. This method is exposed publicly, should it
+	// be needed, as jQuery.event.special.orientationchange.orientation()
+	special_event.orientation = get_orientation = function() {
+		var elem = document.documentElement;
+		return elem && elem.clientWidth / elem.clientHeight < 1.1 ? "portrait" : "landscape";
+	};
+	
+})(jQuery);
+
+$.each({
+	scrollstop: "scrollstart",
+	taphold: "tap",
+	swipeleft: "swipe",
+	swiperight: "swipe"
+}, function( event, sourceEvent ) {
+	$.event.special[ event ] = {
+		setup: function() {
+			$( this ).bind( sourceEvent, $.noop );
+		}
+	};
+});
+
+})( jQuery );
+/*!
+ * jQuery hashchange event - v1.3 - 7/21/2010
+ * http://benalman.com/projects/jquery-hashchange-plugin/
+ * 
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+
+// Script: jQuery hashchange event
+//
+// *Version: 1.3, Last updated: 7/21/2010*
+// 
+// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
+// GitHub       - http://github.com/cowboy/jquery-hashchange/
+// Source       - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
+// (Minified)   - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
+// 
+// About: License
+// 
+// Copyright (c) 2010 "Cowboy" Ben Alman,
+// Dual licensed under the MIT and GPL licenses.
+// http://benalman.com/about/license/
+// 
+// About: Examples
+// 
+// These working examples, complete with fully commented code, illustrate a few
+// ways in which this plugin can be used.
+// 
+// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
+// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
+// 
+// About: Support and Testing
+// 
+// Information about what version or versions of jQuery this plugin has been
+// tested with, what browsers it has been tested in, and where the unit tests
+// reside (so you can test it yourself).
+// 
+// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
+// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
+//                   Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
+// Unit Tests      - http://benalman.com/code/projects/jquery-hashchange/unit/
+// 
+// About: Known issues
+// 
+// While this jQuery hashchange event implementation is quite stable and
+// robust, there are a few unfortunate browser bugs surrounding expected
+// hashchange event-based behaviors, independent of any JavaScript
+// window.onhashchange abstraction. See the following examples for more
+// information:
+// 
+// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
+// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
+// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
+// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
+// 
+// Also note that should a browser natively support the window.onhashchange 
+// event, but not report that it does, the fallback polling loop will be used.
+// 
+// About: Release History
+// 
+// 1.3   - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
+//         "removable" for mobile-only development. Added IE6/7 document.title
+//         support. Attempted to make Iframe as hidden as possible by using
+//         techniques from http://www.paciellogroup.com/blog/?p=604. Added 
+//         support for the "shortcut" format $(window).hashchange( fn ) and
+//         $(window).hashchange() like jQuery provides for built-in events.
+//         Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
+//         lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
+//         and <jQuery.fn.hashchange.src> properties plus document-domain.html
+//         file to address access denied issues when setting document.domain in
+//         IE6/7.
+// 1.2   - (2/11/2010) Fixed a bug where coming back to a page using this plugin
+//         from a page on another domain would cause an error in Safari 4. Also,
+//         IE6/7 Iframe is now inserted after the body (this actually works),
+//         which prevents the page from scrolling when the event is first bound.
+//         Event can also now be bound before DOM ready, but it won't be usable
+//         before then in IE6/7.
+// 1.1   - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
+//         where browser version is incorrectly reported as 8.0, despite
+//         inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
+// 1.0   - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
+//         window.onhashchange functionality into a separate plugin for users
+//         who want just the basic event & back button support, without all the
+//         extra awesomeness that BBQ provides. This plugin will be included as
+//         part of jQuery BBQ, but also be available separately.
+
+(function($,window,undefined){
+  '$:nomunge'; // Used by YUI compressor.
+  
+  // Reused string.
+  var str_hashchange = 'hashchange',
+    
+    // Method / object references.
+    doc = document,
+    fake_onhashchange,
+    special = $.event.special,
+    
+    // Does the browser support window.onhashchange? Note that IE8 running in
+    // IE7 compatibility mode reports true for 'onhashchange' in window, even
+    // though the event isn't supported, so also test document.documentMode.
+    doc_mode = doc.documentMode,
+    supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
+  
+  // Get location.hash (or what you'd expect location.hash to be) sans any
+  // leading #. Thanks for making this necessary, Firefox!
+  function get_fragment( url ) {
+    url = url || location.href;
+    return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
+  };
+  
+  // Method: jQuery.fn.hashchange
+  // 
+  // Bind a handler to the window.onhashchange event or trigger all bound
+  // window.onhashchange event handlers. This behavior is consistent with
+  // jQuery's built-in event handlers.
+  // 
+  // Usage:
+  // 
+  // > jQuery(window).hashchange( [ handler ] );
+  // 
+  // Arguments:
+  // 
+  //  handler - (Function) Optional handler to be bound to the hashchange
+  //    event. This is a "shortcut" for the more verbose form:
+  //    jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
+  //    all bound window.onhashchange event handlers will be triggered. This
+  //    is a shortcut for the more verbose
+  //    jQuery(window).trigger( 'hashchange' ). These forms are described in
+  //    the <hashchange event> section.
+  // 
+  // Returns:
+  // 
+  //  (jQuery) The initial jQuery collection of elements.
+  
+  // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
+  // $(elem).hashchange() for triggering, like jQuery does for built-in events.
+  $.fn[ str_hashchange ] = function( fn ) {
+    return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
+  };
+  
+  // Property: jQuery.fn.hashchange.delay
+  // 
+  // The numeric interval (in milliseconds) at which the <hashchange event>
+  // polling loop executes. Defaults to 50.
+  
+  // Property: jQuery.fn.hashchange.domain
+  // 
+  // If you're setting document.domain in your JavaScript, and you want hash
+  // history to work in IE6/7, not only must this property be set, but you must
+  // also set document.domain BEFORE jQuery is loaded into the page. This
+  // property is only applicable if you are supporting IE6/7 (or IE8 operating
+  // in "IE7 compatibility" mode).
+  // 
+  // In addition, the <jQuery.fn.hashchange.src> property must be set to the
+  // path of the included "document-domain.html" file, which can be renamed or
+  // modified if necessary (note that the document.domain specified must be the
+  // same in both your main JavaScript as well as in this file).
+  // 
+  // Usage:
+  // 
+  // jQuery.fn.hashchange.domain = document.domain;
+  
+  // Property: jQuery.fn.hashchange.src
+  // 
+  // If, for some reason, you need to specify an Iframe src file (for example,
+  // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
+  // do so using this property. Note that when using this property, history
+  // won't be recorded in IE6/7 until the Iframe src file loads. This property
+  // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
+  // compatibility" mode).
+  // 
+  // Usage:
+  // 
+  // jQuery.fn.hashchange.src = 'path/to/file.html';
+  
+  $.fn[ str_hashchange ].delay = 50;
+  /*
+  $.fn[ str_hashchange ].domain = null;
+  $.fn[ str_hashchange ].src = null;
+  */
+  
+  // Event: hashchange event
+  // 
+  // Fired when location.hash changes. In browsers that support it, the native
+  // HTML5 window.onhashchange event is used, otherwise a polling loop is
+  // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
+  // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
+  // compatibility" mode), a hidden Iframe is created to allow the back button
+  // and hash-based history to work.
+  // 
+  // Usage as described in <jQuery.fn.hashchange>:
+  // 
+  // > // Bind an event handler.
+  // > jQuery(window).hashchange( function(e) {
+  // >   var hash = location.hash;
+  // >   ...
+  // > });
+  // > 
+  // > // Manually trigger the event handler.
+  // > jQuery(window).hashchange();
+  // 
+  // A more verbose usage that allows for event namespacing:
+  // 
+  // > // Bind an event handler.
+  // > jQuery(window).bind( 'hashchange', function(e) {
+  // >   var hash = location.hash;
+  // >   ...
+  // > });
+  // > 
+  // > // Manually trigger the event handler.
+  // > jQuery(window).trigger( 'hashchange' );
+  // 
+  // Additional Notes:
+  // 
+  // * The polling loop and Iframe are not created until at least one handler
+  //   is actually bound to the 'hashchange' event.
+  // * If you need the bound handler(s) to execute immediately, in cases where
+  //   a location.hash exists on page load, via bookmark or page refresh for
+  //   example, use jQuery(window).hashchange() or the more verbose 
+  //   jQuery(window).trigger( 'hashchange' ).
+  // * The event can be bound before DOM ready, but since it won't be usable
+  //   before then in IE6/7 (due to the necessary Iframe), recommended usage is
+  //   to bind it inside a DOM ready handler.
+  
+  // Override existing $.event.special.hashchange methods (allowing this plugin
+  // to be defined after jQuery BBQ in BBQ's source code).
+  special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
+    
+    // Called only when the first 'hashchange' event is bound to window.
+    setup: function() {
+      // If window.onhashchange is supported natively, there's nothing to do..
+      if ( supports_onhashchange ) { return false; }
+      
+      // Otherwise, we need to create our own. And we don't want to call this
+      // until the user binds to the event, just in case they never do, since it
+      // will create a polling loop and possibly even a hidden Iframe.
+      $( fake_onhashchange.start );
+    },
+    
+    // Called only when the last 'hashchange' event is unbound from window.
+    teardown: function() {
+      // If window.onhashchange is supported natively, there's nothing to do..
+      if ( supports_onhashchange ) { return false; }
+      
+      // Otherwise, we need to stop ours (if possible).
+      $( fake_onhashchange.stop );
+    }
+    
+  });
+  
+  // fake_onhashchange does all the work of triggering the window.onhashchange
+  // event for browsers that don't natively support it, including creating a
+  // polling loop to watch for hash changes and in IE 6/7 creating a hidden
+  // Iframe to enable back and forward.
+  fake_onhashchange = (function(){
+    var self = {},
+      timeout_id,
+      
+      // Remember the initial hash so it doesn't get triggered immediately.
+      last_hash = get_fragment(),
+      
+      fn_retval = function(val){ return val; },
+      history_set = fn_retval,
+      history_get = fn_retval;
+    
+    // Start the polling loop.
+    self.start = function() {
+      timeout_id || poll();
+    };
+    
+    // Stop the polling loop.
+    self.stop = function() {
+      timeout_id && clearTimeout( timeout_id );
+      timeout_id = undefined;
+    };
+    
+    // This polling loop checks every $.fn.hashchange.delay milliseconds to see
+    // if location.hash has changed, and triggers the 'hashchange' event on
+    // window when necessary.
+    function poll() {
+      var hash = get_fragment(),
+        history_hash = history_get( last_hash );
+      
+      if ( hash !== last_hash ) {
+        history_set( last_hash = hash, history_hash );
+        
+        $(window).trigger( str_hashchange );
+        
+      } else if ( history_hash !== last_hash ) {
+        location.href = location.href.replace( /#.*/, '' ) + history_hash;
+      }
+      
+      timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
+    };
+    
+    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+    // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
+    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+    $.browser.msie && !supports_onhashchange && (function(){
+      // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
+      // when running in "IE7 compatibility" mode.
+      
+      var iframe,
+        iframe_src;
+      
+      // When the event is bound and polling starts in IE 6/7, create a hidden
+      // Iframe for history handling.
+      self.start = function(){
+        if ( !iframe ) {
+          iframe_src = $.fn[ str_hashchange ].src;
+          iframe_src = iframe_src && iframe_src + get_fragment();
+          
+          // Create hidden Iframe. Attempt to make Iframe as hidden as possible
+          // by using techniques from http://www.paciellogroup.com/blog/?p=604.
+          iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
+            
+            // When Iframe has completely loaded, initialize the history and
+            // start polling.
+            .one( 'load', function(){
+              iframe_src || history_set( get_fragment() );
+              poll();
+            })
+            
+            // Load Iframe src if specified, otherwise nothing.
+            .attr( 'src', iframe_src || 'javascript:0' )
+            
+            // Append Iframe after the end of the body to prevent unnecessary
+            // initial page scrolling (yes, this works).
+            .insertAfter( 'body' )[0].contentWindow;
+          
+          // Whenever `document.title` changes, update the Iframe's title to
+          // prettify the back/next history menu entries. Since IE sometimes
+          // errors with "Unspecified error" the very first time this is set
+          // (yes, very useful) wrap this with a try/catch block.
+          doc.onpropertychange = function(){
+            try {
+              if ( event.propertyName === 'title' ) {
+                iframe.document.title = doc.title;
+              }
+            } catch(e) {}
+          };
+          
+        }
+      };
+      
+      // Override the "stop" method since an IE6/7 Iframe was created. Even
+      // if there are no longer any bound event handlers, the polling loop
+      // is still necessary for back/next to work at all!
+      self.stop = fn_retval;
+      
+      // Get history by looking at the hidden Iframe's location.hash.
+      history_get = function() {
+        return get_fragment( iframe.location.href );
+      };
+      
+      // Set a new history item by opening and then closing the Iframe
+      // document, *then* setting its location.hash. If document.domain has
+      // been set, update that as well.
+      history_set = function( hash, history_hash ) {
+        var iframe_doc = iframe.document,
+          domain = $.fn[ str_hashchange ].domain;
+        
+        if ( hash !== history_hash ) {
+          // Update Iframe with any initial `document.title` that might be set.
+          iframe_doc.title = doc.title;
+          
+          // Opening the Iframe's document after it has been closed is what
+          // actually adds a history entry.
+          iframe_doc.open();
+          
+          // Set document.domain for the Iframe document as well, if necessary.
+          domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
+          
+          iframe_doc.close();
+          
+          // Update the Iframe's hash, for great justice.
+          iframe.location.hash = hash;
+        }
+      };
+      
+    })();
+    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
+    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    
+    return self;
+  })();
+  
+})(jQuery,this);
+/*
+* jQuery Mobile Framework : "page" plugin
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+
+$.widget( "mobile.page", $.mobile.widget, {
+	options: {
+		backBtnText: "Back",
+		addBackBtn: true,
+		backBtnTheme: null,
+		degradeInputs: {
+			color: false,
+			date: false,
+			datetime: false,
+			"datetime-local": false,
+			email: false,
+			month: false,
+			number: false,
+			range: "number",
+			search: true,
+			tel: false,
+			time: false,
+			url: false,
+			week: false
+		},
+		keepNative: null
+	},
+
+	_create: function() {
+		var $elem = this.element,
+			o = this.options;
+
+		this.keepNative = ":jqmData(role='none'), :jqmData(role='nojs')" + (o.keepNative ? ", " + o.keepNative : "");
+
+		if ( this._trigger( "beforeCreate" ) === false ) {
+			return;
+		}
+
+		//some of the form elements currently rely on the presence of ui-page and ui-content
+		// classes so we'll handle page and content roles outside of the main role processing
+		// loop below.
+		$elem.find( ":jqmData(role='page'), :jqmData(role='content')" ).andSelf().each(function() {
+			$(this).addClass( "ui-" + $(this).jqmData( "role" ) );
+		});
+
+		$elem.find( ":jqmData(role='nojs')" ).addClass( "ui-nojs" );
+
+		// pre-find data els
+		var $dataEls = $elem.find( ":jqmData(role)" ).andSelf().each(function() {
+			var $this = $( this ),
+				role = $this.jqmData( "role" ),
+				theme = $this.jqmData( "theme" );
+
+			//apply theming and markup modifications to page,header,content,footer
+			if ( role === "header" || role === "footer" ) {
+				$this.addClass( "ui-bar-" + (theme || $this.parent( ":jqmData(role='page')" ).jqmData( "theme" ) || "a") );
+
+				// add ARIA role
+				$this.attr( "role", role === "header" ? "banner" : "contentinfo" );
+
+				//right,left buttons
+				var $headeranchors = $this.children( "a" ),
+					leftbtn = $headeranchors.hasClass( "ui-btn-left" ),
+					rightbtn = $headeranchors.hasClass( "ui-btn-right" );
+
+				if ( !leftbtn ) {
+					leftbtn = $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
+				}
+
+				if ( !rightbtn ) {
+					rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
+				}
+
+				// auto-add back btn on pages beyond first view
+				if ( o.addBackBtn && role === "header" &&
+						$( ".ui-page" ).length > 1 &&
+						$elem.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) &&
+						!leftbtn && $this.jqmData( "backbtn" ) !== false ) {
+
+					var backBtn = $( "<a href='#' class='ui-btn-left' data-"+ $.mobile.ns +"rel='back' data-"+ $.mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" ).prependTo( $this );
+					
+					//if theme is provided, override default inheritance
+					if( o.backBtnTheme ){
+						backBtn.attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme );
+					}
+				}
+
+				//page title
+				$this.children( "h1, h2, h3, h4, h5, h6" )
+					.addClass( "ui-title" )
+					//regardless of h element number in src, it becomes h1 for the enhanced page
+					.attr({ "tabindex": "0", "role": "heading", "aria-level": "1" });
+
+			} else if ( role === "content" ) {
+				if ( theme ) {
+					$this.addClass( "ui-body-" + theme );
+				}
+
+				// add ARIA role
+				$this.attr( "role", "main" );
+
+			} else if ( role === "page" ) {
+				$this.addClass( "ui-body-" + (theme || "c") );
+			}
+
+			switch(role) {
+				case "header":
+				case "footer":
+				case "page":
+				case "content":
+					$this.addClass( "ui-" + role );
+					break;
+				case "collapsible":
+				case "fieldcontain":
+				case "navbar":
+				case "listview":
+				case "dialog":
+					$this[ role ]();
+					break;
+			}
+		});
+
+		//enhance form controls
+  	this._enhanceControls();
+
+		//links in bars, or those with  data-role become buttons
+		$elem.find( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a" )
+			.not( ".ui-btn" )
+			.not(this.keepNative)
+			.buttonMarkup();
+
+		$elem
+			.find(":jqmData(role='controlgroup')")
+			.controlgroup();
+
+		//links within content areas
+		$elem.find( "a:not(.ui-btn):not(.ui-link-inherit)" )
+			.not(this.keepNative)
+			.addClass( "ui-link" );
+
+		//fix toolbars
+		$elem.fixHeaderFooter();
+	},
+
+	_typeAttributeRegex: /\s+type=["']?\w+['"]?/,
+
+	_enhanceControls: function() {
+		var o = this.options, self = this;
+
+		// degrade inputs to avoid poorly implemented native functionality
+		this.element.find( "input" ).not(this.keepNative).each(function() {
+			var type = this.getAttribute( "type" ),
+				optType = o.degradeInputs[ type ] || "text";
+
+			if ( o.degradeInputs[ type ] ) {
+				$( this ).replaceWith(
+					$( "<div>" ).html( $(this).clone() ).html()
+						.replace( self._typeAttributeRegex, " type=\""+ optType +"\" data-" + $.mobile.ns + "type=\""+type+"\" " ) );
+			}
+		});
+
+		// We re-find form elements since the degredation code above
+		// may have injected new elements. We cache the non-native control
+		// query to reduce the number of times we search through the entire page.
+
+		var allControls = this.element.find("input, textarea, select, button"),
+			nonNativeControls = allControls.not(this.keepNative);
+
+		// XXX: Temporary workaround for issue 785. Turn off autocorrect and
+		//      autocomplete since the popup they use can't be dismissed by
+		//      the user. Note that we test for the presence of the feature
+		//      by looking for the autocorrect property on the input element.
+
+		var textInputs = allControls.filter( "input[type=text]" );
+		if (textInputs.length && typeof textInputs[0].autocorrect !== "undefined") {
+			textInputs.each(function(){
+				// Set the attribute instead of the property just in case there
+				// is code that attempts to make modifications via HTML.
+				this.setAttribute("autocorrect", "off");
+				this.setAttribute("autocomplete", "off");
+			});
+		}
+
+		// enchance form controls
+		nonNativeControls
+			.filter( "[type='radio'], [type='checkbox']" )
+			.checkboxradio();
+
+		nonNativeControls
+			.filter( "button, [type='button'], [type='submit'], [type='reset'], [type='image']" )
+			.button();
+
+		nonNativeControls
+			.filter( "input, textarea" )
+			.not( "[type='radio'], [type='checkbox'], [type='button'], [type='submit'], [type='reset'], [type='image'], [type='hidden']" )
+			.textinput();
+
+		nonNativeControls
+			.filter( "input, select" )
+			.filter( ":jqmData(role='slider'), :jqmData(type='range')" )
+			.slider();
+
+		nonNativeControls
+			.filter( "select:not(:jqmData(role='slider'))" )
+			.selectmenu();
+	}
+});
+
+})( jQuery );
+/*!
+ * jQuery Mobile v@VERSION
+ * http://jquerymobile.com/
+ *
+ * Copyright 2010, jQuery Project
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ */
+
+(function( $, window, undefined ) {
+
+	//jQuery.mobile configurable options
+	$.extend( $.mobile, {
+
+		//namespace used framework-wide for data-attrs. Default is no namespace
+		ns: "",
+
+		//define the url parameter used for referencing widget-generated sub-pages.
+		//Translates to to example.html&ui-page=subpageIdentifier
+		//hash segment before &ui-page= is used to make Ajax request
+		subPageUrlKey: "ui-page",
+
+		//anchor links with a data-rel, or pages with a  data-role, that match these selectors will be untrackable in history
+		//(no change in URL, not bookmarkable)
+		nonHistorySelectors: "dialog",
+
+		//class assigned to page currently in view, and during transitions
+		activePageClass: "ui-page-active",
+
+		//class used for "active" button state, from CSS framework
+		activeBtnClass: "ui-btn-active",
+
+		//automatically handle clicks and form submissions through Ajax, when same-domain
+		ajaxEnabled: true,
+
+		//automatically load and show pages based on location.hash
+		hashListeningEnabled: true,
+
+		// TODO: deprecated - remove at 1.0
+		//automatically handle link clicks through Ajax, when possible
+		ajaxLinksEnabled: true,
+
+		// TODO: deprecated - remove at 1.0
+		//automatically handle form submissions through Ajax, when possible
+		ajaxFormsEnabled: true,
+
+		//set default transition - 'none' for no transitions
+		defaultTransition: "slide",
+
+		//show loading message during Ajax requests
+		//if false, message will not appear, but loading classes will still be toggled on html el
+		loadingMessage: "loading",
+		
+		//error response message - appears when an Ajax page request fails
+		pageLoadErrorMessage: "Error Loading Page",
+
+		//configure meta viewport tag's content attr:
+		//note: this feature is deprecated in A4 in favor of adding
+		//the meta viewport element directly in the markup
+		metaViewportContent: "width=device-width, minimum-scale=1, maximum-scale=1",
+
+		//support conditions that must be met in order to proceed
+		//default enhanced qualifications are media query support OR IE 7+
+		gradeA: function(){
+			return $.support.mediaquery || $.mobile.browser.ie && $.mobile.browser.ie >= 7;
+		},
+
+		//TODO might be useful upstream in jquery itself ?
+		keyCode: {
+			ALT: 18,
+			BACKSPACE: 8,
+			CAPS_LOCK: 20,
+			COMMA: 188,
+			COMMAND: 91,
+			COMMAND_LEFT: 91, // COMMAND
+			COMMAND_RIGHT: 93,
+			CONTROL: 17,
+			DELETE: 46,
+			DOWN: 40,
+			END: 35,
+			ENTER: 13,
+			ESCAPE: 27,
+			HOME: 36,
+			INSERT: 45,
+			LEFT: 37,
+			MENU: 93, // COMMAND_RIGHT
+			NUMPAD_ADD: 107,
+			NUMPAD_DECIMAL: 110,
+			NUMPAD_DIVIDE: 111,
+			NUMPAD_ENTER: 108,
+			NUMPAD_MULTIPLY: 106,
+			NUMPAD_SUBTRACT: 109,
+			PAGE_DOWN: 34,
+			PAGE_UP: 33,
+			PERIOD: 190,
+			RIGHT: 39,
+			SHIFT: 16,
+			SPACE: 32,
+			TAB: 9,
+			UP: 38,
+			WINDOWS: 91 // COMMAND
+		},
+
+		//scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
+		silentScroll: function( ypos ) {
+			ypos = ypos || 0;
+			// prevent scrollstart and scrollstop events
+			$.event.special.scrollstart.enabled = false;
+
+			setTimeout(function() {
+				window.scrollTo( 0, ypos );
+				$(document).trigger( "silentscroll", { x: 0, y: ypos });
+			},20);
+
+			setTimeout(function() {
+				$.event.special.scrollstart.enabled = true;
+			}, 150 );
+		}
+	});
+
+	//mobile version of data and removeData and hasData methods
+	//ensures all data is set and retrieved using jQuery Mobile's data namespace
+    $.fn.jqmData = function( prop, value ){
+    	return this.data( prop ? $.mobile.ns + prop : prop, value );
+    };
+    
+    $.jqmData = function( elem, prop, value ){
+    	return $.data( elem, prop && $.mobile.ns + prop, value );
+    };
+    
+    $.fn.jqmRemoveData = function( prop ){
+    	return this.removeData( $.mobile.ns + prop );
+    };
+    
+    $.jqmRemoveData = function( elem, prop ){
+    	return $.removeData( elem, prop && $.mobile.ns + prop );
+    };
+    
+    $.jqmHasData = function( elem, prop ){
+    	return $.hasData( elem, prop && $.mobile.ns + prop );
+    };
+    
+
+	// Monkey-patching Sizzle to filter the :jqmData selector
+	var oldFind = $.find;
+
+	$.find = function( selector, context, ret, extra ) {
+		selector = selector.replace(/:jqmData\(([^)]*)\)/g, "[data-" + ($.mobile.ns || "") + "$1]");
+
+		return oldFind.call( this, selector, context, ret, extra );
+	};
+
+	$.extend( $.find, oldFind );
+
+	$.find.matches = function( expr, set ) {
+		return $.find( expr, null, null, set );
+	};
+
+	$.find.matchesSelector = function( node, expr ) {
+		return $.find( expr, null, null, [node] ).length > 0;
+	};
+})( jQuery, this );
+/*
+* jQuery Mobile Framework : core utilities for auto ajax navigation, base tag mgmt,
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+
+	//define vars for interal use
+	var $window = $(window),
+		$html = $('html'),
+		$head = $('head'),
+
+		//url path helpers for use in relative url management
+		path = {
+
+			//get path from current hash, or from a file path
+			get: function( newPath ){
+				if( newPath === undefined ){
+					newPath = location.hash;
+				}
+				return path.stripHash( newPath ).replace(/[^\/]*\.[^\/*]+$/, '');
+			},
+
+			//return the substring of a filepath before the sub-page key, for making a server request
+			getFilePath: function( path ){
+				var splitkey = '&' + $.mobile.subPageUrlKey;
+				return path && path.split( splitkey )[0].split( dialogHashKey )[0];
+			},
+
+			//set location hash to path
+			set: function( path ){
+				location.hash = path;
+			},
+
+			//location pathname from intial directory request
+			origin: '',
+
+			setOrigin: function(){
+				path.origin = path.get( location.protocol + '//' + location.host + location.pathname );
+			},
+
+			//prefix a relative url with the current path
+			// TODO rename to reflect conditional functionality
+			makeAbsolute: function( url ){
+				// only create an absolute path when the hash can be used as one
+				return path.isPath(window.location.hash) ? path.get() + url : url;
+			},
+
+			// test if a given url (string) is a path
+			// NOTE might be exceptionally naive
+			isPath: function( url ){
+				return /\//.test(url);
+			},
+
+			//return a url path with the window's location protocol/hostname/pathname removed
+			clean: function( url ){
+				// Replace the protocol, host, and pathname only once at the beginning of the url to avoid
+				// problems when it's included as a part of a param
+				// Also, since all urls are absolute in IE, we need to remove the pathname as well.
+				var leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host + location.pathname);
+				return url.replace(leadingUrlRootRegex, "");
+			},
+
+			//just return the url without an initial #
+			stripHash: function( url ){
+				return url.replace( /^#/, "" );
+			},
+
+			//check whether a url is referencing the same domain, or an external domain or different protocol
+			//could be mailto, etc
+			isExternal: function( url ){
+				return path.hasProtocol( path.clean( url ) );
+			},
+
+			hasProtocol: function( url ){
+				return (/^(:?\w+:)/).test( url );
+			},
+
+			//check if the url is relative
+			isRelative: function( url ){
+				return  (/^[^\/|#]/).test( url ) && !path.hasProtocol( url );
+			},
+
+			isEmbeddedPage: function( url ){
+				return (/^#/).test( url );
+			}
+		},
+
+		//will be defined when a link is clicked and given an active class
+		$activeClickedLink = null,
+
+		//urlHistory is purely here to make guesses at whether the back or forward button was clicked
+		//and provide an appropriate transition
+		urlHistory = {
+			//array of pages that are visited during a single page load. each has a url and optional transition
+			stack: [],
+
+			//maintain an index number for the active page in the stack
+			activeIndex: 0,
+
+			//get active
+			getActive: function(){
+				return urlHistory.stack[ urlHistory.activeIndex ];
+			},
+
+			getPrev: function(){
+				return urlHistory.stack[ urlHistory.activeIndex - 1 ];
+			},
+
+			getNext: function(){
+				return urlHistory.stack[ urlHistory.activeIndex + 1 ];
+			},
+
+			// addNew is used whenever a new page is added
+			addNew: function( url, transition, title, storedTo ){
+				//if there's forward history, wipe it
+				if( urlHistory.getNext() ){
+					urlHistory.clearForward();
+				}
+
+				urlHistory.stack.push( {url : url, transition: transition, title: title, page: storedTo } );
+				
+				urlHistory.activeIndex = urlHistory.stack.length - 1;
+			},
+
+			//wipe urls ahead of active index
+			clearForward: function(){
+				urlHistory.stack = urlHistory.stack.slice( 0, urlHistory.activeIndex + 1 );
+			},
+
+			directHashChange: function(opts){
+				var back , forward, newActiveIndex;
+
+				// check if url isp in history and if it's ahead or behind current page
+				$.each( urlHistory.stack, function( i, historyEntry ){
+
+					//if the url is in the stack, it's a forward or a back
+					if( opts.currentUrl === historyEntry.url ){
+						//define back and forward by whether url is older or newer than current page
+						back = i < urlHistory.activeIndex;
+						forward = !back;
+						newActiveIndex = i;
+					}
+				});
+
+				// save new page index, null check to prevent falsey 0 result
+				this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex;
+
+				if( back ){
+					opts.isBack();
+				} else if( forward ){
+					opts.isForward();
+				}
+			},
+
+			//disable hashchange event listener internally to ignore one change
+			//toggled internally when location.hash is updated to match the url of a successful page load
+			ignoreNextHashChange: true
+		},
+
+		//define first selector to receive focus when a page is shown
+		focusable = "[tabindex],a,button:visible,select:visible,input",
+
+		//contains role for next page, if defined on clicked link via data-rel
+		nextPageRole = null,
+
+		//queue to hold simultanious page transitions
+		pageTransitionQueue = [],
+
+		// indicates whether or not page is in process of transitioning
+		isPageTransitioning = false,
+
+		//nonsense hash change key for dialogs, so they create a history entry
+		dialogHashKey = "&ui-state=dialog",
+
+		//existing base tag?
+		$base = $head.children("base"),
+		hostURL = location.protocol + '//' + location.host,
+		docLocation = path.get( hostURL + location.pathname ),
+		docBase = docLocation;
+
+		if ($base.length){
+			var href = $base.attr("href");
+			if (href){
+				if (href.search(/^[^:\/]+:\/\/[^\/]+\/?/) === -1){
+					//the href is not absolute, we need to turn it into one
+					//so that we can turn paths stored in our location hash into
+					//relative paths.
+					if (href.charAt(0) === '/'){
+						//site relative url
+						docBase = hostURL + href;
+					}
+					else {
+						//the href is a document relative url
+						docBase = docLocation + href;
+						//XXX: we need some code here to calculate the final path
+						// just in case the docBase contains up-level (../) references.
+					}
+				}
+				else {
+					//the href is an absolute url
+					docBase = href;
+				}
+			}
+			//make sure docBase ends with a slash
+			docBase = docBase  + (docBase.charAt(docBase.length - 1) === '/' ? ' ' : '/');
+		}
+
+		//base element management, defined depending on dynamic base tag support
+		var base = $.support.dynamicBaseTag ? {
+
+			//define base element, for use in routing asset urls that are referenced in Ajax-requested markup
+			element: ($base.length ? $base : $("<base>", { href: docBase }).prependTo( $head )),
+
+			//set the generated BASE element's href attribute to a new page's base path
+			set: function( href ){
+				base.element.attr('href', docBase + path.get( href ));
+			},
+
+			//set the generated BASE element's href attribute to a new page's base path
+			reset: function(){
+				base.element.attr('href', docBase );
+			}
+
+		} : undefined;
+
+
+
+		//set location pathname from intial directory request
+		path.setOrigin();
+
+/*
+	internal utility functions
+--------------------------------------*/
+
+
+	//direct focus to the page title, or otherwise first focusable element
+	function reFocus( page ){
+		var pageTitle = page.find( ".ui-title:eq(0)" );
+		if( pageTitle.length ){
+			pageTitle.focus();
+		}
+		else{
+			page.find( focusable ).eq(0).focus();
+		}
+	}
+
+	//remove active classes after page transition or error
+	function removeActiveLinkClass( forceRemoval ){
+		if( !!$activeClickedLink && (!$activeClickedLink.closest( '.ui-page-active' ).length || forceRemoval )){
+			$activeClickedLink.removeClass( $.mobile.activeBtnClass );
+		}
+		$activeClickedLink = null;
+	}
+
+	//animation complete callback
+	$.fn.animationComplete = function( callback ){
+		if($.support.cssTransitions){
+			return $(this).one('webkitAnimationEnd', callback);
+		}
+		else{
+			// defer execution for consistency between webkit/non webkit
+			setTimeout(callback, 0);
+			return $(this);
+		}
+	};
+
+
+
+/* exposed $.mobile methods	 */
+
+	//update location.hash, with or without triggering hashchange event
+	//TODO - deprecate this one at 1.0
+	$.mobile.updateHash = path.set;
+
+	//expose path object on $.mobile
+	$.mobile.path = path;
+
+	//expose base object on $.mobile
+	$.mobile.base = base;
+
+	//url stack, useful when plugins need to be aware of previous pages viewed
+	//TODO: deprecate this one at 1.0
+	$.mobile.urlstack = urlHistory.stack;
+
+	//history stack
+	$.mobile.urlHistory = urlHistory;
+
+	//enable cross-domain page support
+	$.mobile.allowCrossDomainPages = false;
+
+	// changepage function
+	$.mobile.changePage = function( to, transition, reverse, changeHash, fromHashChange ){
+		//from is always the currently viewed page
+		var toIsArray = $.type(to) === "array",
+			toIsObject = $.type(to) === "object",
+			from = toIsArray ? to[0] : $.mobile.activePage;
+
+			to = toIsArray ? to[1] : to;
+
+		var url = $.type(to) === "string" ? path.stripHash( to ) : "",
+			fileUrl = url,
+			data,
+			type = 'get',
+			isFormRequest = false,
+			duplicateCachedPage = null,
+			currPage = urlHistory.getActive(),
+			back = false,
+			forward = false
+			pageTitle = document.title;
+
+
+		// If we are trying to transition to the same page that we are currently on ignore the request.
+		// an illegal same page request is defined by the current page being the same as the url, as long as there's history
+		// and to is not an array or object (those are allowed to be "same")
+		if( currPage && urlHistory.stack.length > 1 && currPage.url === url && !toIsArray && !toIsObject ) {
+			return;
+		}
+		else if(isPageTransitioning) {
+			pageTransitionQueue.unshift(arguments);
+			return;
+		}
+
+		isPageTransitioning = true;
+
+		// if the changePage was sent from a hashChange event guess if it came from the history menu
+		// and match the transition accordingly
+		if( fromHashChange ){
+			urlHistory.directHashChange({
+				currentUrl: url,
+				isBack: function(){
+					forward = !(back = true);
+					reverse = true;
+					transition = transition || currPage.transition;
+				},
+				isForward: function(){
+					forward = !(back = false);
+					transition = transition || urlHistory.getActive().transition;
+				}
+			});
+
+			//TODO forward = !back was breaking for some reason
+		}
+
+		if( toIsObject && to.url ){
+			url = to.url;
+			data = to.data;
+			type = to.type;
+			isFormRequest = true;
+			//make get requests bookmarkable
+			if( data && type === 'get' ){
+				if($.type( data ) === "object" ){
+					data = $.param(data);
+				}
+
+				url += "?" + data;
+				data = undefined;
+			}
+		}
+
+		//reset base to pathname for new request
+		if(base){ base.reset(); }
+
+		//kill the keyboard
+		$( window.document.activeElement ).add( "input:focus, textarea:focus, select:focus" ).blur();
+
+		function defaultTransition(){
+			if(transition === undefined){
+				transition = ( nextPageRole && nextPageRole === 'dialog' ) ? 'pop' : $.mobile.defaultTransition;
+			}
+		}
+
+		function releasePageTransitionLock(){
+			isPageTransitioning = false;
+			if(pageTransitionQueue.length>0) {
+				$.mobile.changePage.apply($.mobile, pageTransitionQueue.pop());
+			}
+		}
+
+		//function for transitioning between two existing pages
+		function transitionPages() {
+		    $.mobile.silentScroll();
+
+			//get current scroll distance
+			var currScroll = $window.scrollTop(),
+					perspectiveTransitions = [ "flip" ],
+					pageContainerClasses = [];
+
+			//support deep-links to generated sub-pages
+			if( url.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ){
+				to = $( ":jqmData(url='" + url + "')" );
+			}
+
+			if( from ){
+				//set as data for returning to that spot
+				from.jqmData( "lastScroll", currScroll);
+				//trigger before show/hide events
+				from.data( "page" )._trigger( "beforehide", null, { nextPage: to } );
+			}
+			to.data( "page" )._trigger( "beforeshow", null, { prevPage: from || $("") } );
+
+			function loadComplete(){
+
+				if( changeHash !== false && url ){
+					//disable hash listening temporarily
+					urlHistory.ignoreNextHashChange = false;
+					//update hash and history
+					path.set( url );
+				}
+				
+				//if title element wasn't found, try the page div data attr too
+				var newPageTitle = to.attr( ":jqmData(title)" ) || to.find(".ui-header .ui-title" ).text();
+				if( !!newPageTitle && pageTitle == document.title ){
+					pageTitle = newPageTitle;
+				}
+
+				//add page to history stack if it's not back or forward
+				if( !back && !forward ){
+					urlHistory.addNew( url, transition, pageTitle, to );
+				}
+				
+				//set page title
+				document.title = urlHistory.getActive().title;
+
+				removeActiveLinkClass();
+
+				//jump to top or prev scroll, sometimes on iOS the page has not rendered yet.  I could only get by this with a setTimeout, but would like to avoid that.
+				$.mobile.silentScroll( to.jqmData( "lastScroll" ) );
+
+				reFocus( to );
+
+				//trigger show/hide events
+				if( from ){
+					from.data( "page" )._trigger( "hide", null, { nextPage: to } );
+				}
+				//trigger pageshow, define prevPage as either from or empty jQuery obj
+				to.data( "page" )._trigger( "show", null, { prevPage: from || $("") } );
+
+				//set "to" as activePage
+				$.mobile.activePage = to;
+
+				//if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
+				if (duplicateCachedPage !== null) {
+				    duplicateCachedPage.remove();
+				}
+
+				//remove initial build class (only present on first pageshow)
+				$html.removeClass( "ui-mobile-rendering" );
+
+				releasePageTransitionLock();
+			}
+
+			function addContainerClass(className){
+				$.mobile.pageContainer.addClass(className);
+				pageContainerClasses.push(className);
+			}
+
+			function removeContainerClasses(){
+				$.mobile
+					.pageContainer
+					.removeClass(pageContainerClasses.join(" "));
+
+				pageContainerClasses = [];
+			}
+
+			if(transition && (transition !== 'none')){
+			    $.mobile.pageLoading( true );
+				if( $.inArray(transition, perspectiveTransitions) >= 0 ){
+					addContainerClass('ui-mobile-viewport-perspective');
+				}
+
+				addContainerClass('ui-mobile-viewport-transitioning');
+
+				if( from ){
+					from.addClass( transition + " out " + ( reverse ? "reverse" : "" ) );
+				}
+				to.addClass( $.mobile.activePageClass + " " + transition +
+					" in " + ( reverse ? "reverse" : "" ) );
+
+				// callback - remove classes, etc
+				to.animationComplete(function() {
+					to.add(from).removeClass("out in reverse " + transition );
+					if( from ){
+						from.removeClass( $.mobile.activePageClass );
+					}
+					loadComplete();
+					removeContainerClasses();
+				});
+			}
+			else{
+			    $.mobile.pageLoading( true );
+			    if( from ){
+					from.removeClass( $.mobile.activePageClass );
+				}
+				to.addClass( $.mobile.activePageClass );
+				loadComplete();
+			}
+		}
+
+		//shared page enhancements
+		function enhancePage(){
+
+			//set next page role, if defined
+			if ( nextPageRole || to.jqmData('role') === 'dialog' ) {
+				url = urlHistory.getActive().url + dialogHashKey;
+				if(nextPageRole){
+					to.attr( "data-" + $.mobile.ns + "role", nextPageRole );
+					nextPageRole = null;
+				}
+			}
+
+			//run page plugin
+			to.page();
+		}
+
+		//if url is a string
+		if( url ){
+			to = $( ":jqmData(url='" + url + "')" );
+			fileUrl = path.getFilePath(url);
+		}
+		else{ //find base url of element, if avail
+			var toID = to.attr( "data-" + $.mobile.ns + "url" ),
+				toIDfileurl = path.getFilePath(toID);
+
+			if(toID !== toIDfileurl){
+				fileUrl = toIDfileurl;
+			}
+		}
+
+		// ensure a transition has been set where pop is undefined
+		defaultTransition();
+
+		// find the "to" page, either locally existing in the dom or by creating it through ajax
+		if ( to.length && !isFormRequest ) {
+			if( fileUrl && base ){
+				base.set( fileUrl );
+			}
+			enhancePage();
+			transitionPages();
+		} else {
+
+			//if to exists in DOM, save a reference to it in duplicateCachedPage for removal after page change
+			if( to.length ){
+				duplicateCachedPage = to;
+			}
+
+			$.mobile.pageLoading();
+
+			$.ajax({
+				url: fileUrl,
+				type: type,
+				data: data,
+				success: function( html ) {
+					//pre-parse html to check for a data-url,
+					//use it as the new fileUrl, base path, etc
+					var all = $("<div></div>"),
+							redirectLoc,
+							
+							//page title regexp
+							newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1,
+							
+							// TODO handle dialogs again
+							pageElemRegex = new RegExp(".*(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>).*"),
+							dataUrlRegex = new RegExp("\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?");
+							
+
+					// data-url must be provided for the base tag so resource requests can be directed to the
+					// correct url. loading into a temprorary element makes these requests immediately
+					if(pageElemRegex.test(html) && RegExp.$1 && dataUrlRegex.test(RegExp.$1) && RegExp.$1) {
+						redirectLoc = RegExp.$1;
+					}
+
+					if( redirectLoc ){
+						if(base){
+							base.set( redirectLoc );
+						}
+						url = fileUrl = path.getFilePath( redirectLoc );
+					}
+					else {
+						if(base){
+							base.set(fileUrl);
+						}
+					}
+
+					//workaround to allow scripts to execute when included in page divs
+					all.get(0).innerHTML = html;
+					to = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();
+					
+					//finally, if it's defined now, set the page title for storage in urlHistory
+					if( newPageTitle ){
+						pageTitle = newPageTitle;
+					}					
+
+					//rewrite src and href attrs to use a base url
+					if( !$.support.dynamicBaseTag ){
+						var newPath = path.get( fileUrl );
+						to.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function(){
+							var thisAttr = $(this).is('[href]') ? 'href' : 'src',
+								thisUrl = $(this).attr(thisAttr);
+														
+
+							//if full path exists and is same, chop it - helps IE out
+							thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' );
+
+							if( !/^(\w+:|#|\/)/.test(thisUrl) ){
+								$(this).attr(thisAttr, newPath + thisUrl);
+							}
+						});
+					}
+
+					//append to page and enhance
+					to
+						.attr( "data-" + $.mobile.ns + "url", fileUrl )
+						.appendTo( $.mobile.pageContainer );
+
+					enhancePage();
+					setTimeout(function() { transitionPages(); }, 0);
+				},
+				error: function() {
+
+					//remove loading message
+					$.mobile.pageLoading( true );
+
+					//clear out the active button state
+					removeActiveLinkClass(true);
+
+					//set base back to current path
+					if( base ){
+						base.set( path.get() );
+					}
+
+					//release transition lock so navigation is free again
+					releasePageTransitionLock();
+
+					//show error message
+					$("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>"+ $.mobile.pageLoadErrorMessage +"</h1></div>")
+						.css({ "display": "block", "opacity": 0.96, "top": $(window).scrollTop() + 100 })
+						.appendTo( $.mobile.pageContainer )
+						.delay( 800 )
+						.fadeOut( 400, function(){
+							$(this).remove();
+						});
+				}
+			});
+		}
+
+	};
+
+
+/* Event Bindings - hashchange, submit, and click */
+
+	//bind to form submit events, handle with Ajax
+	$( "form" ).live('submit', function(event){
+		if( !$.mobile.ajaxEnabled ||
+			//TODO: deprecated - remove at 1.0
+			!$.mobile.ajaxFormsEnabled ||
+			$(this).is( ":jqmData(ajax='false')" ) ){ return; }
+
+		var type = $(this).attr("method"),
+			url = path.clean( $(this).attr( "action" ) ),
+			target = $(this).attr("target");
+
+		//external submits use regular HTTP
+		if( path.isExternal( url ) || target ){
+			return;
+		}
+
+		//if it's a relative href, prefix href with base url
+		if( path.isRelative( url ) ){
+			url = path.makeAbsolute( url );
+		}
+
+		$.mobile.changePage({
+				url: url.length && url || path.get(),
+				type: type.length && type.toLowerCase() || "get",
+				data: $(this).serialize()
+			},
+			$(this).jqmData("transition"),
+			$(this).jqmData("direction"),
+			true
+		);
+		event.preventDefault();
+	});
+
+
+	//temporary fix for allowing 3rd party onclick handlers to still function.
+	var preventClickDefault = false, stopClickPropagation = false;
+
+	//click routing - direct to HTTP or Ajax, accordingly
+	$( "a" ).live( "vclick", function(event) {
+
+		var $this = $(this),
+
+			//get href, if defined, otherwise fall to null #
+			href = $this.attr( "href" ) || "#",
+
+			//cache a check for whether the link had a protocol
+			//if this is true and the link was same domain, we won't want
+			//to prefix the url with a base (esp helpful in IE, where every
+			//url is absolute
+			hadProtocol = path.hasProtocol( href ),
+
+			//get href, remove same-domain protocol and host
+			url = path.clean( href ),
+
+			//rel set to external
+			isRelExternal = $this.is( "[rel='external']" ),
+
+			//rel set to external
+			isEmbeddedPage = path.isEmbeddedPage( url ),
+
+			// Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
+			// requests if the document doing the request was loaded via the file:// protocol.
+			// This is usually to allow the application to "phone home" and fetch app specific
+			// data. We normally let the browser handle external/cross-domain urls, but if the
+			// allowCrossDomainPages option is true, we will allow cross-domain http/https
+			// requests to go through our page loading logic.
+			isCrossDomainPageLoad = ($.mobile.allowCrossDomainPages && location.protocol === "file:" && url.search(/^https?:/) != -1),
+
+			//check for protocol or rel and its not an embedded page
+			//TODO overlap in logic from isExternal, rel=external check should be
+			//     moved into more comprehensive isExternalLink
+			isExternal = (path.isExternal(url) && !isCrossDomainPageLoad) || (isRelExternal && !isEmbeddedPage),
+
+			//if target attr is specified we mimic _blank... for now
+			hasTarget = $this.is( "[target]" ),
+
+			//if data-ajax attr is set to false, use the default behavior of a link
+			hasAjaxDisabled = $this.is( ":jqmData(ajax='false')" );
+
+		//reset our prevDefault value because I'm paranoid.
+		preventClickDefault = stopClickPropagation = false;
+
+		//if there's a data-rel=back attr, go back in history
+		if( $this.is( ":jqmData(rel='back')" ) ){
+			window.history.back();
+			preventClickDefault = stopClickPropagation = true;
+			return;
+		}
+
+		//prevent # urls from bubbling
+		//path.get() is replaced to combat abs url prefixing in IE
+		if( url.replace(path.get(), "") == "#"  ){
+			//for links created purely for interaction - ignore
+			//don't call preventDefault on the event here, vclick
+			//may have been triggered by a touchend, before any moues
+			//click event was dispatched and we want to make sure
+			//3rd party onclick handlers get triggered. If and when
+			//a mouse click event is generated, our live("click") handler
+			//will get triggered and do the preventDefault.
+			preventClickDefault = true;
+			return;
+		}
+
+		$activeClickedLink = $this.closest( ".ui-btn" ).addClass( $.mobile.activeBtnClass );
+
+		if( isExternal || hasAjaxDisabled || hasTarget || !$.mobile.ajaxEnabled ||
+			// TODO: deprecated - remove at 1.0
+			!$.mobile.ajaxLinksEnabled ){
+			//remove active link class if external (then it won't be there if you come back)
+			window.setTimeout(function() {removeActiveLinkClass(true);}, 200);
+
+			//use default click handling
+			return;
+		}
+
+		//use ajax
+		var transition = $this.jqmData( "transition" ),
+			direction = $this.jqmData("direction"),
+			reverse = (direction && direction === "reverse") ||
+			// deprecated - remove by 1.0
+			$this.jqmData( "back" );
+
+		//this may need to be more specific as we use data-rel more
+		nextPageRole = $this.attr( "data-" + $.mobile.ns + "rel" );
+
+		//if it's a relative href, prefix href with base url
+		if( path.isRelative( url ) && !hadProtocol ){
+			url = path.makeAbsolute( url );
+		}
+
+		url = path.stripHash( url );
+
+		$.mobile.changePage( url, transition, reverse);
+		preventClickDefault = true;
+	});
+
+	$( "a" ).live( "click", function(event) {
+		if (preventClickDefault){
+			event.preventDefault();
+			preventClickDefault = false;
+		}
+		if (stopClickPropagation){
+			event.stopPropagation();
+			stopClickPropagation = false;
+		}
+	});
+
+	//hashchange event handler
+	$window.bind( "hashchange", function( e, triggered ) {
+		//find first page via hash
+		var to = path.stripHash( location.hash ),
+			//transition is false if it's the first page, undefined otherwise (and may be overridden by default)
+			transition = $.mobile.urlHistory.stack.length === 0 ? false : undefined;
+
+		//if listening is disabled (either globally or temporarily), or it's a dialog hash
+		if( !$.mobile.hashListeningEnabled || !urlHistory.ignoreNextHashChange ){
+			if( !urlHistory.ignoreNextHashChange ){
+				urlHistory.ignoreNextHashChange = true;
+			}
+
+			return;
+		}
+
+		// special case for dialogs
+		if( urlHistory.stack.length > 1 &&
+				to.indexOf( dialogHashKey ) > -1 ){
+
+			// If current active page is not a dialog skip the dialog and continue
+			// in the same direction
+			if(!$.mobile.activePage.is( ".ui-dialog" )) {
+				//determine if we're heading forward or backward and continue accordingly past
+				//the current dialog
+				urlHistory.directHashChange({
+					currentUrl: to,
+					isBack: function(){ window.history.back(); },
+					isForward: function(){ window.history.forward(); }
+				});
+
+				// prevent changepage
+				return;
+			} else {
+				var setTo = function(){ to = $.mobile.urlHistory.getActive().page; };
+				// if the current active page is a dialog and we're navigating
+				// to a dialog use the dialog objected saved in the stack
+				urlHistory.directHashChange({	currentUrl: to, isBack: setTo, isForward: setTo	});
+			}
+		}
+
+		//if to is defined, load it
+		if ( to ){
+			$.mobile.changePage( to, transition, undefined, false, true );
+		}
+		//there's no hash, go to the first page in the dom
+		else {
+			$.mobile.changePage( $.mobile.firstPage, transition, true, false, true );
+		}
+		});
+
+})( jQuery );
+/*
+* jQuery Mobile Framework : "fixHeaderFooter" plugin - on-demand positioning for headers,footers
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+$.fn.fixHeaderFooter = function(options){
+	if( !$.support.scrollTop ){ return this; }
+	
+	return this.each(function(){
+		var $this = $(this);
+		
+		if( $this.jqmData('fullscreen') ){ $this.addClass('ui-page-fullscreen'); }
+		$this.find( ".ui-header:jqmData(position='fixed')" ).addClass('ui-header-fixed ui-fixed-inline fade'); //should be slidedown
+		$this.find( ".ui-footer:jqmData(position='fixed')" ).addClass('ui-footer-fixed ui-fixed-inline fade'); //should be slideup		
+	});
+};
+
+//single controller for all showing,hiding,toggling		
+$.fixedToolbars = (function(){
+	if( !$.support.scrollTop ){ return; }
+	var currentstate = 'inline',
+		autoHideMode = false,
+		showDelay = 100,
+		delayTimer,
+		ignoreTargets = 'a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed',
+		toolbarSelector = '.ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last',
+		stickyFooter, //for storing quick references to duplicate footers
+		supportTouch = $.support.touch,
+		touchStartEvent = supportTouch ? "touchstart" : "mousedown",
+		touchStopEvent = supportTouch ? "touchend" : "mouseup",
+		stateBefore = null,
+		scrollTriggered = false,
+        touchToggleEnabled = true;
+
+	function showEventCallback(event)
+	{
+		// An event that affects the dimensions of the visual viewport has
+		// been triggered. If the header and/or footer for the current page are in overlay
+		// mode, we want to hide them, and then fire off a timer to show them at a later
+		// point. Events like a resize can be triggered continuously during a scroll, on
+		// some platforms, so the timer is used to delay the actual positioning until the
+		// flood of events have subsided.
+		//
+		// If we are in autoHideMode, we don't do anything because we know the scroll
+		// callbacks for the plugin will fire off a show when the scrolling has stopped.
+		if (!autoHideMode && currentstate == 'overlay') {
+			if (!delayTimer)
+				$.fixedToolbars.hide(true);
+			$.fixedToolbars.startShowTimer();
+		}
+	}
+
+	$(function() {
+		$(document)
+			.bind( "vmousedown",function(event){
+				if( touchToggleEnabled ) {
+					stateBefore = currentstate;
+				}
+			})
+			.bind( "vclick",function(event){
+				if( touchToggleEnabled ) {
+					if( $(event.target).closest(ignoreTargets).length ){ return; }
+					if( !scrollTriggered ){
+						$.fixedToolbars.toggle(stateBefore);
+						stateBefore = null;
+					}
+				}
+			})
+			.bind('scrollstart',function(event){
+				scrollTriggered = true;
+				if(stateBefore == null){ stateBefore = currentstate; }
+
+				// We only enter autoHideMode if the headers/footers are in
+				// an overlay state or the show timer was started. If the
+				// show timer is set, clear it so the headers/footers don't
+				// show up until after we're done scrolling.
+				var isOverlayState = stateBefore == 'overlay';
+				autoHideMode = isOverlayState || !!delayTimer;
+				if (autoHideMode){
+					$.fixedToolbars.clearShowTimer();
+					if (isOverlayState) {
+						$.fixedToolbars.hide(true);
+					}
+				}
+			})
+			.bind('scrollstop',function(event){
+				if( $(event.target).closest(ignoreTargets).length ){ return; }
+				scrollTriggered = false;
+				if (autoHideMode) {
+					autoHideMode = false;
+					$.fixedToolbars.startShowTimer();
+				}
+				stateBefore = null;
+			})
+			.bind('silentscroll', showEventCallback);
+
+			$(window).bind('resize', showEventCallback);
+	});
+		
+	//before page is shown, check for duplicate footer
+	$('.ui-page').live('pagebeforeshow', function(event, ui){
+		var page = $(event.target),
+			footer = page.find( ":jqmData(role='footer')" ),
+			id = footer.data('id'),
+			prevPage = ui.prevPage;
+		
+		prevFooter = prevPage && prevPage.find( ":jqmData(role='footer')" );
+		var prevFooterMatches = prevFooter.jqmData( "id" ) === id;
+		
+		if( id && prevFooterMatches ){
+			stickyFooter = footer;
+			setTop( stickyFooter.removeClass( "fade in out" ).appendTo( $.mobile.pageContainer ) );
+		}
+	});
+
+	//after page is shown, append footer to new page
+	$('.ui-page').live('pageshow', function(event, ui){
+		var $this = $(this);
+		
+		if( stickyFooter && stickyFooter.length ){	
+			
+			setTimeout(function(){
+				setTop( stickyFooter.appendTo( $this ).addClass("fade") );
+				stickyFooter = null;
+			}, 500);	
+		}
+		
+		$.fixedToolbars.show(true, this);	
+	});
+
+	
+	// element.getBoundingClientRect() is broken in iOS 3.2.1 on the iPad. The
+	// coordinates inside of the rect it returns don't have the page scroll position
+	// factored out of it like the other platforms do. To get around this,
+	// we'll just calculate the top offset the old fashioned way until core has
+	// a chance to figure out how to handle this situation.
+	//
+	// TODO: We'll need to get rid of getOffsetTop() once a fix gets folded into core.
+
+	function getOffsetTop(ele)
+	{
+		var top = 0;
+		if (ele)
+		{
+			var op = ele.offsetParent, body = document.body;
+			top = ele.offsetTop;
+			while (ele && ele != body)
+			{
+				top += ele.scrollTop || 0;
+				if (ele == op)
+				{
+					top += op.offsetTop;
+					op = ele.offsetParent;
+				}
+				ele = ele.parentNode;
+			}
+		}
+		return top;
+	}
+
+	function setTop(el){
+		var fromTop = $(window).scrollTop(),
+			thisTop = getOffsetTop(el[0]), // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead.
+			thisCSStop = el.css('top') == 'auto' ? 0 : parseFloat(el.css('top')),
+			screenHeight = window.innerHeight,
+			thisHeight = el.outerHeight(),
+			useRelative = el.parents('.ui-page:not(.ui-page-fullscreen)').length,
+			relval;
+		if( el.is('.ui-header-fixed') ){
+			relval = fromTop - thisTop + thisCSStop;
+			if( relval < thisTop){ relval = 0; }
+			return el.css('top', ( useRelative ) ? relval : fromTop);
+		}
+		else{
+			//relval = -1 * (thisTop - (fromTop + screenHeight) + thisCSStop + thisHeight);
+			//if( relval > thisTop ){ relval = 0; }
+			relval = fromTop + screenHeight - thisHeight - (thisTop - thisCSStop);
+			return el.css('top', ( useRelative ) ? relval : fromTop + screenHeight - thisHeight );
+		}
+	}
+
+	//exposed methods
+	return {
+		show: function(immediately, page){
+			$.fixedToolbars.clearShowTimer();
+			currentstate = 'overlay';
+			var $ap = page ? $(page) : ($.mobile.activePage ? $.mobile.activePage : $(".ui-page-active"));
+			return $ap.children( toolbarSelector ).each(function(){
+				var el = $(this),
+					fromTop = $(window).scrollTop(),
+					thisTop = getOffsetTop(el[0]), // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead.
+					screenHeight = window.innerHeight,
+					thisHeight = el.outerHeight(),
+					alreadyVisible = (el.is('.ui-header-fixed') && fromTop <= thisTop + thisHeight) || (el.is('.ui-footer-fixed') && thisTop <= fromTop + screenHeight);	
+				
+				//add state class
+				el.addClass('ui-fixed-overlay').removeClass('ui-fixed-inline');	
+					
+				if( !alreadyVisible && !immediately ){
+					el.animationComplete(function(){
+						el.removeClass('in');
+					}).addClass('in');
+				}
+				setTop(el);
+			});	
+		},
+		hide: function(immediately){
+			currentstate = 'inline';
+			var $ap = $.mobile.activePage ? $.mobile.activePage : $(".ui-page-active");
+			return $ap.children( toolbarSelector ).each(function(){
+				var el = $(this);
+
+				var thisCSStop = el.css('top'); thisCSStop = thisCSStop == 'auto' ? 0 : parseFloat(thisCSStop);
+				
+				//add state class
+				el.addClass('ui-fixed-inline').removeClass('ui-fixed-overlay');
+				
+				if (thisCSStop < 0 || (el.is('.ui-header-fixed') && thisCSStop != 0))
+				{
+					if(immediately){
+						el.css('top',0);
+					}
+					else{
+						if( el.css('top') !== 'auto' && parseFloat(el.css('top')) !== 0 ){
+							var classes = 'out reverse';
+							el.animationComplete(function(){
+								el.removeClass(classes);
+								el.css('top',0);
+							}).addClass(classes);	
+						}
+					}
+				}
+			});
+		},
+		startShowTimer: function(){
+			$.fixedToolbars.clearShowTimer();
+			var args = $.makeArray(arguments);
+			delayTimer = setTimeout(function(){
+				delayTimer = undefined;
+				$.fixedToolbars.show.apply(null, args);
+			}, showDelay);
+		},
+		clearShowTimer: function() {
+			if (delayTimer) {
+				clearTimeout(delayTimer);
+			}
+			delayTimer = undefined;
+		},
+		toggle: function(from){
+			if(from){ currentstate = from; }
+			return (currentstate == 'overlay') ? $.fixedToolbars.hide() : $.fixedToolbars.show();
+		},
+        setTouchToggleEnabled: function(enabled) {
+            touchToggleEnabled = enabled;
+        }
+	};
+})();
+
+})(jQuery);
+/*
+* jQuery Mobile Framework : "checkboxradio" plugin
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+$.widget( "mobile.checkboxradio", $.mobile.widget, {
+	options: {
+		theme: null
+	},
+	_create: function(){
+		var self = this,
+			input = this.element,
+			//NOTE: Windows Phone could not find the label through a selector
+			//filter works though.
+			label = input.closest("form,fieldset,:jqmData(role='page')").find("label").filter("[for=" + input[0].id + "]"),
+			inputtype = input.attr( "type" ),
+			checkedicon = "ui-icon-" + inputtype + "-on",
+			uncheckedicon = "ui-icon-" + inputtype + "-off";
+
+		if ( inputtype != "checkbox" && inputtype != "radio" ) { return; }
+
+		//expose for other methods
+		$.extend( this,{
+			label			: label,
+			inputtype		: inputtype,
+			checkedicon		: checkedicon,
+			uncheckedicon	: uncheckedicon
+		});
+
+		// If there's no selected theme...
+		if( !this.options.theme ) {
+			this.options.theme = this.element.jqmData( "theme" );
+		}
+
+		label
+			.buttonMarkup({
+				theme: this.options.theme,
+				icon: this.element.parents( ":jqmData(type='horizontal')" ).length ? undefined : uncheckedicon,
+				shadow: false
+			});
+
+		// wrap the input + label in a div
+		input
+			.add( label )
+			.wrapAll( "<div class='ui-" + inputtype +"'></div>" );
+
+		label.bind({
+			vmouseover: function() {
+				if( $(this).parent().is('.ui-disabled') ){ return false; }
+			},
+
+			vclick: function( event ){
+				if ( input.is( ":disabled" ) ){
+					event.preventDefault();
+					return;
+				}
+
+				self._cacheVals();
+				input.attr( "checked", inputtype === "radio" && true || !input.is( ":checked" ) );
+				self._updateAll();
+				return false;
+			}
+
+		});
+
+		input
+			.bind({
+				vmousedown: function(){
+					this._cacheVals();
+				},
+
+				vclick: function(){
+					self._updateAll();
+				},
+
+				focus: function() {
+					label.addClass( "ui-focus" );
+				},
+
+				blur: function() {
+					label.removeClass( "ui-focus" );
+				}
+			});
+
+		this.refresh();
+
+	},
+
+	_cacheVals: function(){
+		this._getInputSet().each(function(){
+			$(this).jqmData("cacheVal", $(this).is(":checked") );
+		});
+	},
+
+	//returns either a set of radios with the same name attribute, or a single checkbox
+	_getInputSet: function(){
+		return this.element.closest( "form,fieldset,:jqmData(role='page')" )
+				.find( "input[name='"+ this.element.attr( "name" ) +"'][type='"+ this.inputtype +"']" );
+	},
+
+	_updateAll: function(){
+		this._getInputSet().each(function(){
+			if( $(this).is(":checked") || this.inputtype === "checkbox" ){
+				$(this).trigger("change");
+			}
+		})
+		.checkboxradio( "refresh" );
+	},
+
+	refresh: function( ){
+		var input = this.element,
+			label = this.label,
+			icon = label.find( ".ui-icon" );
+
+		if ( input[0].checked ) {
+			label.addClass( $.mobile.activeBtnClass );
+			icon.addClass( this.checkedicon ).removeClass( this.uncheckedicon );
+
+		} else {
+			label.removeClass( $.mobile.activeBtnClass );
+			icon.removeClass( this.checkedicon ).addClass( this.uncheckedicon );
+		}
+
+		if( input.is( ":disabled" ) ){
+			this.disable();
+		}
+		else {
+			this.enable();
+		}
+	},
+
+	disable: function(){
+		this.element.attr("disabled",true).parent().addClass("ui-disabled");
+	},
+
+	enable: function(){
+		this.element.attr("disabled",false).parent().removeClass("ui-disabled");
+	}
+});
+})( jQuery );
+/*
+* jQuery Mobile Framework : "textinput" plugin for text inputs, textareas
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+$.widget( "mobile.textinput", $.mobile.widget, {
+	options: {
+		theme: null
+	},
+	_create: function(){
+		var input = this.element,
+			o = this.options,
+			theme = o.theme,
+			themeclass;
+			
+		if ( !theme ) {
+			var themedParent = this.element.closest("[class*='ui-bar-'],[class*='ui-body-']"); 
+				theme = themedParent.length ?
+					/ui-(bar|body)-([a-z])/.exec( themedParent.attr("class") )[2] :
+					"c";
+		}	
+		
+		themeclass = " ui-body-" + theme;
+		
+		$('label[for='+input.attr('id')+']').addClass('ui-input-text');
+		
+		input.addClass('ui-input-text ui-body-'+ o.theme);
+		
+		var focusedEl = input;
+		
+		//"search" input widget
+		if( input.is( "[type='search'],:jqmData(type='search')" ) ){
+			focusedEl = input.wrap('<div class="ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield'+ themeclass +'"></div>').parent();
+			var clearbtn = $('<a href="#" class="ui-input-clear" title="clear text">clear text</a>')
+				.tap(function( e ){
+					input.val('').focus();
+					input.trigger('change'); 
+					clearbtn.addClass('ui-input-clear-hidden');
+					e.preventDefault();
+				})
+				.appendTo(focusedEl)
+				.buttonMarkup({icon: 'delete', iconpos: 'notext', corners:true, shadow:true});
+			
+			function toggleClear(){
+				if(input.val() == ''){
+					clearbtn.addClass('ui-input-clear-hidden');
+				}
+				else{
+					clearbtn.removeClass('ui-input-clear-hidden');
+				}
+			}
+			
+			toggleClear();
+			input.keyup(toggleClear);	
+		}
+		else{
+			input.addClass('ui-corner-all ui-shadow-inset' + themeclass);
+		}
+				
+		input
+			.focus(function(){
+				focusedEl.addClass('ui-focus');
+			})
+			.blur(function(){
+				focusedEl.removeClass('ui-focus');
+			});	
+			
+		//autogrow
+		if ( input.is('textarea') ) {
+			var extraLineHeight = 15,
+				keyupTimeoutBuffer = 100,
+				keyup = function() {
+					var scrollHeight = input[0].scrollHeight,
+						clientHeight = input[0].clientHeight;
+					if ( clientHeight < scrollHeight ) {
+						input.css({ height: (scrollHeight + extraLineHeight) });
+					}
+				},
+				keyupTimeout;
+			input.keyup(function() {
+				clearTimeout( keyupTimeout );
+				keyupTimeout = setTimeout( keyup, keyupTimeoutBuffer );
+			});
+		}
+	},
+	
+	disable: function(){
+		( this.element.attr("disabled",true).is( "[type='search'],:jqmData(type='search')" ) ? this.element.parent() : this.element ).addClass("ui-disabled");
+	},
+	
+	enable: function(){
+		( this.element.attr("disabled", false).is( "[type='search'],:jqmData(type='search')" ) ? this.element.parent() : this.element ).removeClass("ui-disabled");
+	}
+});
+})( jQuery );
+/*
+* jQuery Mobile Framework : "selectmenu" plugin
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+$.widget( "mobile.selectmenu", $.mobile.widget, {
+	options: {
+		theme: null,
+		disabled: false,
+		icon: 'arrow-d',
+		iconpos: 'right',
+		inline: null,
+		corners: true,
+		shadow: true,
+		iconshadow: true,
+		menuPageTheme: 'b',
+		overlayTheme: 'a',
+		hidePlaceholderMenuItems: true,
+		closeText: 'Close',
+		nativeMenu: true
+	},
+	_create: function(){
+		
+		var self = this,
+
+			o = this.options,
+
+			select = this.element
+						.wrap( "<div class='ui-select'>" ),
+
+			selectID = select.attr( "id" ),
+
+			label = $( "label[for="+ selectID +"]" ).addClass( "ui-select" ),
+			
+			//IE throws an exception at options.item() function when
+			//there is no selected item
+			//select first in this case 
+			selectedIndex = select[0].selectedIndex == -1 ? 0 : select[0].selectedIndex,
+			
+			button = ( self.options.nativeMenu ? $( "<div/>" ) : $( "<a>", {
+					"href": "#",
+					"role": "button",
+					"id": buttonId,
+					"aria-haspopup": "true",
+					"aria-owns": menuId
+				}) )
+				.text( $( select[0].options.item( selectedIndex ) ).text() )
+				.insertBefore( select )
+				.buttonMarkup({
+					theme: o.theme,
+					icon: o.icon,
+					iconpos: o.iconpos,
+					inline: o.inline,
+					corners: o.corners,
+					shadow: o.shadow,
+					iconshadow: o.iconshadow
+				}),
+
+			//multi select or not
+			isMultiple = self.isMultiple = select[0].multiple;
+
+		//Opera does not properly support opacity on select elements
+		//In Mini, it hides the element, but not its text
+		//On the desktop,it seems to do the opposite
+		//for these reasons, using the nativeMenu option results in a full native select in Opera
+		if( o.nativeMenu && window.opera && window.opera.version ){
+			select.addClass( "ui-select-nativeonly" );
+		}
+
+		//vars for non-native menus
+		if( !o.nativeMenu ){
+			var options = select.find("option"),
+
+				buttonId = selectID + "-button",
+
+				menuId = selectID + "-menu",
+
+				thisPage = select.closest( ".ui-page" ),
+
+				//button theme
+				theme = /ui-btn-up-([a-z])/.exec( button.attr("class") )[1],
+
+				menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' data-" +$.mobile.ns + "theme='"+ o.menuPageTheme +"'>" +
+							"<div data-" + $.mobile.ns + "role='header'>" +
+								"<div class='ui-title'>" + label.text() + "</div>"+
+							"</div>"+
+							"<div data-" + $.mobile.ns + "role='content'></div>"+
+						"</div>" )
+						.appendTo( $.mobile.pageContainer )
+						.page(),
+
+				menuPageContent = menuPage.find( ".ui-content" ),
+
+				menuPageClose = menuPage.find( ".ui-header a" ),
+
+				screen = $( "<div>", {"class": "ui-selectmenu-screen ui-screen-hidden"})
+							.appendTo( thisPage ),
+
+				listbox = $( "<div>", { "class": "ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all pop ui-body-" + o.overlayTheme } )
+						.insertAfter(screen),
+
+				list = $( "<ul>", {
+						"class": "ui-selectmenu-list",
+						"id": menuId,
+						"role": "listbox",
+						"aria-labelledby": buttonId
+					})
+					.attr( "data-" + $.mobile.ns + "theme", theme )
+					.appendTo( listbox ),
+
+				header = $( "<div>", {
+						"class": "ui-header ui-bar-" + theme
+					})
+					.prependTo( listbox ),
+
+				headerTitle = $( "<h1>", {
+						"class": "ui-title"
+					})
+					.appendTo( header ),
+
+				headerClose = $( "<a>", {
+						"text": o.closeText,
+						"href": "#",
+						"class": "ui-btn-left"
+					})
+					.attr( "data-" + $.mobile.ns + "iconpos", "notext" )
+					.attr( "data-" + $.mobile.ns + "icon", "delete" )
+					.appendTo( header )
+					.buttonMarkup(),
+
+				menuType;
+		} //end non native vars
+
+		// add counter for multi selects
+		if( isMultiple ){
+			self.buttonCount = $('<span>')
+				.addClass( 'ui-li-count ui-btn-up-c ui-btn-corner-all' )
+				.hide()
+				.appendTo( button );
+		}
+
+		//disable if specified
+		if( o.disabled ){ this.disable(); }
+
+		//events on native select
+		select
+			.change(function(){
+				self.refresh();
+			});
+
+		//expose to other methods
+		$.extend(self, {
+			select: select,
+			optionElems: options,
+			selectID: selectID,
+			label: label,
+			buttonId:buttonId,
+			menuId:menuId,
+			thisPage:thisPage,
+			button:button,
+			menuPage:menuPage,
+			menuPageContent:menuPageContent,
+			screen:screen,
+			listbox:listbox,
+			list:list,
+			menuType:menuType,
+			header:header,
+			headerClose:headerClose,
+			headerTitle:headerTitle,
+			placeholder: ''
+		});
+
+		//support for using the native select menu with a custom button
+		if( o.nativeMenu ){
+
+			select
+				.appendTo(button)
+				.bind( "vmousedown", function( e ){
+					//add active class to button
+					button.addClass( $.mobile.activeBtnClass );
+				})
+				.bind( "focus vmouseover", function(){
+					button.trigger( "vmouseover" );
+				})
+				.bind( "vmousemove", function(){
+					//remove active class on scroll/touchmove
+					button.removeClass( $.mobile.activeBtnClass );
+				})
+				.bind( "change blur vmouseout", function(){
+					button
+						.trigger( "vmouseout" )
+						.removeClass( $.mobile.activeBtnClass );
+				});
+
+
+		} else {
+
+			//create list from select, update state
+			self.refresh();
+
+			select
+				.attr( "tabindex", "-1" )
+				.focus(function(){
+					$(this).blur();
+					button.focus();
+				});	
+
+			//button events
+			button
+				.bind( "vclick keydown" , function( event ){
+					if( event.type == "vclick" || 
+						event.keyCode && ( event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE ) ){
+						self.open();
+						event.preventDefault();
+					}
+				});
+
+			//events for list items
+			list
+			.attr( "role", "listbox" )
+			.delegate( ".ui-li>a", "focusin", function() {
+				$( this ).attr( "tabindex", "0" );
+			})
+			.delegate( ".ui-li>a", "focusout", function() {
+				$( this ).attr( "tabindex", "-1" );
+			})
+			.delegate("li:not(.ui-disabled, .ui-li-divider)", "vclick", function(event){
+
+				// index of option tag to be selected
+				var oldIndex = select[0].selectedIndex,
+					newIndex = list.find( "li:not(.ui-li-divider)" ).index( this ),
+					option = self.optionElems.eq( newIndex )[0];
+
+				// toggle selected status on the tag for multi selects
+				option.selected = isMultiple ? !option.selected : true;
+
+				// toggle checkbox class for multiple selects
+				if( isMultiple ){
+					$(this)
+						.find('.ui-icon')
+						.toggleClass('ui-icon-checkbox-on', option.selected)
+						.toggleClass('ui-icon-checkbox-off', !option.selected);
+				}
+
+				// trigger change if value changed
+				if( oldIndex !== newIndex ){
+					select.trigger( "change" );
+				}
+
+				//hide custom select for single selects only
+				if( !isMultiple ){
+					self.close();
+				}
+
+				event.preventDefault();
+			})
+			//keyboard events for menu items
+			.keydown(function( e ) {
+				var target = $( e.target ),
+					li = target.closest( "li" );
+	
+				// switch logic based on which key was pressed
+				switch ( e.keyCode ) {
+					// up or left arrow keys
+					case 38:
+						var prev = li.prev();
+	
+						// if there's a previous option, focus it
+						if ( prev.length ) {
+							target
+								.blur()
+								.attr( "tabindex", "-1" );
+	
+							prev.find( "a" ).first().focus();
+						}	
+	
+						return false;
+					break;
+	
+					// down or right arrow keys
+					case 40:
+						var next = li.next();
+					
+						// if there's a next option, focus it
+						if ( next.length ) {
+							target
+								.blur()
+								.attr( "tabindex", "-1" );
+							
+							next.find( "a" ).first().focus();
+						}	
+	
+						return false;
+					break;
+	
+					// if enter or space is pressed, trigger click
+					case 13:
+					case 32:
+						 target.trigger( "vclick" );
+	
+						 return false;
+					break;	
+				}
+			});	
+
+			//events on "screen" overlay
+			screen.bind("vclick", function( event ){
+				self.close();
+			});
+			
+			//close button on small overlays
+			self.headerClose.click(function(){
+				if( self.menuType == "overlay" ){
+					self.close();
+					return false;
+				}
+			})
+		}
+	},
+
+	_buildList: function(){
+		var self = this,
+			o = this.options,
+			placeholder = this.placeholder,
+			optgroups = [],
+			lis = [],
+			dataIcon = self.isMultiple ? "checkbox-off" : "false";
+
+		self.list.empty().filter('.ui-listview').listview('destroy');
+
+		//populate menu with options from select element
+		self.select.find( "option" ).each(function( i ){
+			var $this = $(this),
+				$parent = $this.parent(),
+				text = $this.text(),
+				anchor = "<a href='#'>"+ text +"</a>",
+				classes = [],
+				extraAttrs = [];
+
+			// are we inside an optgroup?
+			if( $parent.is("optgroup") ){
+				var optLabel = $parent.attr("label");
+
+				// has this optgroup already been built yet?
+				if( $.inArray(optLabel, optgroups) === -1 ){
+					lis.push( "<li data-" + $.mobile.ns + "role='list-divider'>"+ optLabel +"</li>" );
+					optgroups.push( optLabel );
+				}
+			}
+
+			//find placeholder text
+			if( !this.getAttribute('value') || text.length == 0 || $this.jqmData('placeholder') ){
+				if( o.hidePlaceholderMenuItems ){
+					classes.push( "ui-selectmenu-placeholder" );
+				}
+				placeholder = self.placeholder = text;
+			}
+
+			// support disabled option tags
+			if( this.disabled ){
+				classes.push( "ui-disabled" );
+				extraAttrs.push( "aria-disabled='true'" );
+			}
+
+			lis.push( "<li data-" + $.mobile.ns + "icon='"+ dataIcon +"' class='"+ classes.join(" ") + "' " + extraAttrs.join(" ") +">"+ anchor +"</li>" )
+		});
+
+		self.list.html( lis.join(" ") );
+		
+		self.list.find( "li" )
+			.attr({ "role": "option", "tabindex": "-1" })
+			.first().attr( "tabindex", "0" );
+
+		// hide header close link for single selects
+		if( !this.isMultiple ){
+			this.headerClose.hide();
+		}
+
+		// hide header if it's not a multiselect and there's no placeholder
+		if( !this.isMultiple && !placeholder.length ){
+			this.header.hide();
+		} else {
+			this.headerTitle.text( this.placeholder );
+		}
+
+		//now populated, create listview
+		self.list.listview();
+	},
+
+	refresh: function( forceRebuild ){
+		var self = this,
+			select = this.element,
+			isMultiple = this.isMultiple,
+			options = this.optionElems = select.find("option"),
+			selected = options.filter(":selected"),
+
+			// return an array of all selected index's
+			indicies = selected.map(function(){
+				return options.index( this );
+			}).get();
+
+		if( !self.options.nativeMenu && ( forceRebuild || select[0].options.length != self.list.find('li').length )){
+			self._buildList();
+		}
+
+		self.button
+			.find( ".ui-btn-text" )
+			.text(function(){
+				if( !isMultiple ){
+					return selected.text();
+				}
+
+				return selected.length ?
+					selected.map(function(){ return $(this).text(); }).get().join(', ') :
+					self.placeholder;
+			});
+
+		// multiple count inside button
+		if( isMultiple ){
+			self.buttonCount[ selected.length > 1 ? 'show' : 'hide' ]().text( selected.length );
+		}
+
+		if( !self.options.nativeMenu ){
+			self.list
+				.find( 'li:not(.ui-li-divider)' )
+				.removeClass( $.mobile.activeBtnClass )
+				.attr( 'aria-selected', false )
+				.each(function( i ){
+					if( $.inArray(i, indicies) > -1 ){
+						var item = $(this).addClass( $.mobile.activeBtnClass );
+
+						// aria selected attr
+						item.find( 'a' ).attr( 'aria-selected', true );
+
+						// multiple selects: add the "on" checkbox state to the icon
+						if( isMultiple ){
+							item.find('.ui-icon').removeClass('ui-icon-checkbox-off').addClass('ui-icon-checkbox-on');
+						}
+					}
+				});
+		}
+	},
+
+	open: function(){
+		if( this.options.disabled || this.options.nativeMenu ){ return; }
+
+		var self = this,
+			menuHeight = self.list.parent().outerHeight(),
+			menuWidth = self.list.parent().outerWidth(),
+			scrollTop = $(window).scrollTop(),
+			btnOffset = self.button.offset().top,
+			screenHeight = window.innerHeight,
+			screenWidth = window.innerWidth;
+
+		//add active class to button
+		self.button.addClass( $.mobile.activeBtnClass );
+
+		//remove after delay
+		setTimeout(function(){
+			self.button.removeClass( $.mobile.activeBtnClass );
+		}, 300);
+
+		function focusMenuItem(){
+			self.list.find( ".ui-btn-active" ).focus();
+		}
+
+		if( menuHeight > screenHeight - 80 || !$.support.scrollTop ){
+
+			//for webos (set lastscroll using button offset)
+			if( scrollTop == 0 && btnOffset > screenHeight ){
+				self.thisPage.one('pagehide',function(){
+					$(this).jqmData('lastScroll', btnOffset);
+				});
+			}
+
+			self.menuPage.one('pageshow', function() {
+				// silentScroll() is called whenever a page is shown to restore
+				// any previous scroll position the page may have had. We need to
+				// wait for the "silentscroll" event before setting focus to avoid
+				// the browser's "feature" which offsets rendering to make sure
+				// whatever has focus is in view.
+				$(window).one("silentscroll", function(){ focusMenuItem(); });
+			});
+
+			self.menuType = "page";
+			self.menuPageContent.append( self.list );
+			$.mobile.changePage(self.menuPage, 'pop', false, true);
+		}
+		else {
+			self.menuType = "overlay";
+
+			self.screen
+				.height( $(document).height() )
+				.removeClass('ui-screen-hidden');
+
+			//try and center the overlay over the button
+			var roomtop = btnOffset - scrollTop,
+				roombot = scrollTop + screenHeight - btnOffset,
+				halfheight = menuHeight / 2,
+				maxwidth = parseFloat(self.list.parent().css('max-width')),
+				newtop, newleft;
+
+			if( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ){
+				newtop = btnOffset + ( self.button.outerHeight() / 2 ) - halfheight;
+			}
+			else{
+				//30px tolerance off the edges
+				newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30;
+			}
+
+			// if the menuwidth is smaller than the screen center is
+			if (menuWidth < maxwidth) {
+				newleft = (screenWidth - menuWidth) / 2;
+			} else { //otherwise insure a >= 30px offset from the left
+				newleft = self.button.offset().left + self.button.outerWidth() / 2 - menuWidth / 2;
+				// 30px tolerance off the edges
+				if (newleft < 30) {
+					newleft = 30;
+				} else if ((newleft + menuWidth) > screenWidth) {
+					newleft = screenWidth - menuWidth - 30;
+				}
+			}
+
+			self.listbox
+				.append( self.list )
+				.removeClass( "ui-selectmenu-hidden" )
+				.css({
+					top: newtop,
+					left: newleft
+				})
+				.addClass("in");
+
+			focusMenuItem();
+		}
+
+		// wait before the dialog can be closed
+		setTimeout(function(){
+		 	self.isOpen = true;
+		}, 400);
+	},
+
+	close: function(){
+		if( this.options.disabled || !this.isOpen || this.options.nativeMenu ){ return; }
+		var self = this;
+
+		function focusButton(){
+			setTimeout(function(){
+				self.button.focus();
+			}, 40);
+
+			self.listbox.removeAttr('style').append( self.list );
+		}
+
+		if(self.menuType == "page"){
+			$.mobile.changePage([self.menuPage,self.thisPage], 'pop', true, false);
+			self.menuPage.one("pagehide", focusButton);
+		}
+		else{
+			self.screen.addClass( "ui-screen-hidden" );
+			self.listbox.addClass( "ui-selectmenu-hidden" ).removeAttr( "style" ).removeClass("in");
+			focusButton();
+		}
+
+		// allow the dialog to be closed again
+		this.isOpen = false;
+	},
+
+	disable: function(){
+		this.element.attr("disabled",true);
+		this.button.addClass('ui-disabled').attr("aria-disabled", true);
+		return this._setOption( "disabled", true );
+	},
+
+	enable: function(){
+		this.element.attr("disabled",false);
+		this.button.removeClass('ui-disabled').attr("aria-disabled", false);
+		return this._setOption( "disabled", false );
+	}
+});
+})( jQuery );
+
+/*
+* jQuery Mobile Framework : plugin for making button-like links
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+
+$.fn.buttonMarkup = function( options ){
+	return this.each( function() {
+		var el = $( this ),
+		    o = $.extend( {}, $.fn.buttonMarkup.defaults, el.jqmData(), options),
+
+			// Classes Defined
+			buttonClass,
+			innerClass = "ui-btn-inner",
+			iconClass;
+
+		if ( attachEvents ) {
+			attachEvents();
+		}
+
+		// if not, try to find closest theme container
+		if ( !o.theme ) {
+			var themedParent = el.closest("[class*='ui-bar-'],[class*='ui-body-']");
+			o.theme = themedParent.length ?
+				/ui-(bar|body)-([a-z])/.exec( themedParent.attr("class") )[2] :
+				"c";
+		}
+
+		buttonClass = "ui-btn ui-btn-up-" + o.theme;
+
+		if ( o.inline ) {
+			buttonClass += " ui-btn-inline";
+		}
+
+		if ( o.icon ) {
+			o.icon = "ui-icon-" + o.icon;
+			o.iconpos = o.iconpos || "left";
+
+			iconClass = "ui-icon " + o.icon;
+
+			if ( o.shadow ) {
+				iconClass += " ui-icon-shadow";
+			}
+		}
+
+		if ( o.iconpos ) {
+			buttonClass += " ui-btn-icon-" + o.iconpos;
+
+			if ( o.iconpos == "notext" && !el.attr("title") ) {
+				el.attr( "title", el.text() );
+			}
+		}
+
+		if ( o.corners ) {
+			buttonClass += " ui-btn-corner-all";
+			innerClass += " ui-btn-corner-all";
+		}
+
+		if ( o.shadow ) {
+			buttonClass += " ui-shadow";
+		}
+
+		el
+			.attr( "data-" + $.mobile.ns + "theme", o.theme )
+			.addClass( buttonClass );
+
+		var wrap = ("<D class='" + innerClass + "'><D class='ui-btn-text'></D>" +
+			( o.icon ? "<span class='" + iconClass + "'></span>" : "" ) +
+			"</D>").replace(/D/g, o.wrapperEls);
+
+		el.wrapInner( wrap );
+	});
+};
+
+$.fn.buttonMarkup.defaults = {
+	corners: true,
+	shadow: true,
+	iconshadow: true,
+	wrapperEls: "span"
+};
+
+var attachEvents = function() {
+	$(".ui-btn:not(.ui-disabled)").live({
+		"vmousedown": function() {
+			var theme = $(this).attr( "data-" + $.mobile.ns + "theme" );
+			$(this).removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-down-" + theme );
+		},
+		"vmousecancel vmouseup": function() {
+			var theme = $(this).attr( "data-" + $.mobile.ns + "theme" );
+			$(this).removeClass( "ui-btn-down-" + theme ).addClass( "ui-btn-up-" + theme );
+		},
+		"vmouseover focus": function() {
+			var theme = $(this).attr( "data-" + $.mobile.ns + "theme" );
+			$(this).removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-hover-" + theme );
+		},
+		"vmouseout blur": function() {
+			var theme = $(this).attr( "data-" + $.mobile.ns + "theme" );
+			$(this).removeClass( "ui-btn-hover-" + theme ).addClass( "ui-btn-up-" + theme );
+		}
+	});
+
+	attachEvents = null;
+};
+
+})(jQuery);
+/*
+* jQuery Mobile Framework : "button" plugin - links that proxy to native input/buttons
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/ 
+(function($, undefined ) {
+$.widget( "mobile.button", $.mobile.widget, {
+	options: {
+		theme: null, 
+		icon: null,
+		iconpos: null,
+		inline: null,
+		corners: true,
+		shadow: true,
+		iconshadow: true
+	},
+	_create: function(){
+		var $el = this.element,
+			o = this.options;
+		
+		//add ARIA role
+		this.button = $( "<div></div>" )
+			.text( $el.text() || $el.val() )
+			.buttonMarkup({
+				theme: o.theme, 
+				icon: o.icon,
+				iconpos: o.iconpos,
+				inline: o.inline,
+				corners: o.corners,
+				shadow: o.shadow,
+				iconshadow: o.iconshadow
+			})
+			.insertBefore( $el )
+			.append( $el.addClass('ui-btn-hidden') );
+		
+		//add hidden input during submit
+		var type = $el.attr('type');
+		if( type !== 'button' && type !== 'reset' ){
+			$el.bind("vclick", function(){
+				var $buttonPlaceholder = $("<input>", 
+						{type: "hidden", name: $el.attr("name"), value: $el.attr("value")})
+						.insertBefore($el);
+						
+				//bind to doc to remove after submit handling	
+				$(document).submit(function(){
+					 $buttonPlaceholder.remove();
+				});
+			});
+		}
+		this.refresh();
+			
+	},
+
+	enable: function(){
+		this.element.attr("disabled", false);
+		this.button.removeClass("ui-disabled").attr("aria-disabled", false);
+		return this._setOption("disabled", false);
+	},
+
+	disable: function(){
+		this.element.attr("disabled", true);
+		this.button.addClass("ui-disabled").attr("aria-disabled", true);
+		return this._setOption("disabled", true);
+	},
+
+	refresh: function(){
+		if( this.element.attr('disabled') ){
+			this.disable();
+		}
+		else{
+			this.enable();
+		}
+	}
+});
+})( jQuery );/*
+* jQuery Mobile Framework : "slider" plugin
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+$.widget( "mobile.slider", $.mobile.widget, {
+	options: {
+		theme: null,
+		trackTheme: null,
+		disabled: false
+	},
+	_create: function(){
+		var self = this,
+
+			control = this.element,
+
+			parentTheme = control.parents('[class*=ui-bar-],[class*=ui-body-]').eq(0),
+
+			parentTheme = parentTheme.length ? parentTheme.attr('class').match(/ui-(bar|body)-([a-z])/)[2] : 'c',
+
+			theme = this.options.theme ? this.options.theme : parentTheme,
+
+			trackTheme = this.options.trackTheme ? this.options.trackTheme : parentTheme,
+
+			cType = control[0].nodeName.toLowerCase(),
+			selectClass = (cType == 'select') ? 'ui-slider-switch' : '',
+			controlID = control.attr('id'),
+			labelID = controlID + '-label',
+			label = $('[for='+ controlID +']').attr('id',labelID),
+			val = function(){
+				return (cType == 'input') ? parseFloat(control.val()) : control[0].selectedIndex;
+			},
+			min = (cType == 'input') ? parseFloat(control.attr('min')) : 0,
+			max = (cType == 'input') ? parseFloat(control.attr('max')) : control.find('option').length-1,
+			step = window.parseFloat(control.attr('step') || 1),
+			slider = $('<div class="ui-slider '+ selectClass +' ui-btn-down-'+ trackTheme+' ui-btn-corner-all" role="application"></div>'),
+			handle = $('<a href="#" class="ui-slider-handle"></a>')
+				.appendTo(slider)
+				.buttonMarkup({corners: true, theme: theme, shadow: true})
+				.attr({
+					'role': 'slider',
+					'aria-valuemin': min,
+					'aria-valuemax': max,
+					'aria-valuenow': val(),
+					'aria-valuetext': val(),
+					'title': val(),
+					'aria-labelledby': labelID
+				});
+
+		$.extend(this, {
+			slider: slider,
+			handle: handle,
+			dragging: false,
+			beforeStart: null
+		});
+
+		if(cType == 'select'){
+			slider.wrapInner('<div class="ui-slider-inneroffset"></div>');
+			var options = control.find('option');
+
+			control.find('option').each(function(i){
+				var side = (i==0) ?'b':'a',
+					corners = (i==0) ? 'right' :'left',
+					theme = (i==0) ? ' ui-btn-down-' + trackTheme :' ui-btn-active';
+				$('<div class="ui-slider-labelbg ui-slider-labelbg-'+ side + theme +' ui-btn-corner-'+ corners+'"></div>').prependTo(slider);
+				$('<span class="ui-slider-label ui-slider-label-'+ side + theme +' ui-btn-corner-'+ corners+'" role="img">'+$(this).text()+'</span>').prependTo(handle);
+			});
+
+		}
+
+		label.addClass('ui-slider');
+
+		// monitor the input for updated values
+		control
+			.addClass((cType == 'input') ? 'ui-slider-input' : 'ui-slider-switch')
+			.change(function(){
+				self.refresh( val(), true );
+			})
+			.keyup(function(){ // necessary?
+				self.refresh( val(), true, true );
+			})
+			.blur(function(){
+				self.refresh( val(), true );
+			});
+
+		// prevent screen drag when slider activated
+		$(document).bind( "vmousemove", function(event){
+			if ( self.dragging ) {
+				self.refresh( event );
+				return false;
+			}
+		});
+
+		slider
+			.bind( "vmousedown", function(event){
+				self.dragging = true;
+				if ( cType === "select" ) {
+					self.beforeStart = control[0].selectedIndex;
+				}
+				self.refresh( event );
+				return false;
+			});
+
+		slider
+			.add(document)
+			.bind( "vmouseup", function(){
+				if ( self.dragging ) {
+					self.dragging = false;
+					if ( cType === "select" ) {
+						if ( self.beforeStart === control[0].selectedIndex ) {
+							//tap occurred, but value didn't change. flip it!
+							self.refresh( self.beforeStart === 0 ? 1 : 0 );
+						}
+						var curval = val();
+						var snapped = Math.round( curval / (max - min) * 100 );
+						handle
+							.addClass("ui-slider-handle-snapping")
+							.css("left", snapped + "%")
+							.animationComplete(function(){
+								handle.removeClass("ui-slider-handle-snapping");
+							});
+					}
+					return false;
+				}
+			});
+
+		slider.insertAfter(control);
+
+		// NOTE force focus on handle
+		this.handle
+			.bind( "vmousedown", function(){
+				$(this).focus();
+			});
+
+		this.handle
+			.bind( "keydown", function( event ) {
+				var index = val();
+
+				if ( self.options.disabled ) {
+					return;
+				}
+
+				// In all cases prevent the default and mark the handle as active
+				switch ( event.keyCode ) {
+				 case $.mobile.keyCode.HOME:
+				 case $.mobile.keyCode.END:
+				 case $.mobile.keyCode.PAGE_UP:
+				 case $.mobile.keyCode.PAGE_DOWN:
+				 case $.mobile.keyCode.UP:
+				 case $.mobile.keyCode.RIGHT:
+				 case $.mobile.keyCode.DOWN:
+				 case $.mobile.keyCode.LEFT:
+					event.preventDefault();
+
+					if ( !self._keySliding ) {
+						self._keySliding = true;
+						$( this ).addClass( "ui-state-active" );
+					}
+					break;
+				}
+
+				// move the slider according to the keypress
+				switch ( event.keyCode ) {
+				 case $.mobile.keyCode.HOME:
+					self.refresh(min);
+					break;
+				 case $.mobile.keyCode.END:
+					self.refresh(max);
+					break;
+				 case $.mobile.keyCode.PAGE_UP:
+				 case $.mobile.keyCode.UP:
+				 case $.mobile.keyCode.RIGHT:
+					self.refresh(index + step);
+					break;
+				 case $.mobile.keyCode.PAGE_DOWN:
+				 case $.mobile.keyCode.DOWN:
+				 case $.mobile.keyCode.LEFT:
+					self.refresh(index - step);
+					break;
+				}
+			}) // remove active mark
+			.keyup(function( event ) {
+				if ( self._keySliding ) {
+					self._keySliding = false;
+					$( this ).removeClass( "ui-state-active" );
+				}
+			});
+
+		this.refresh();
+	},
+
+	refresh: function(val, isfromControl, preventInputUpdate){
+		if ( this.options.disabled ) { return; }
+
+		var control = this.element, percent,
+			cType = control[0].nodeName.toLowerCase(),
+			min = (cType === "input") ? parseFloat(control.attr("min")) : 0,
+			max = (cType === "input") ? parseFloat(control.attr("max")) : control.find("option").length - 1;
+
+		if ( typeof val === "object" ) {
+			var data = val,
+				// a slight tolerance helped get to the ends of the slider
+				tol = 8;
+			if ( !this.dragging
+					|| data.pageX < this.slider.offset().left - tol
+					|| data.pageX > this.slider.offset().left + this.slider.width() + tol ) {
+				return;
+			}
+			percent = Math.round( ((data.pageX - this.slider.offset().left) / this.slider.width() ) * 100 );
+		} else {
+			if ( val == null ) {
+				val = (cType === "input") ? parseFloat(control.val()) : control[0].selectedIndex;
+			}
+			percent = (parseFloat(val) - min) / (max - min) * 100;
+		}
+
+		if ( isNaN(percent) ) { return; }
+		if ( percent < 0 ) { percent = 0; }
+		if ( percent > 100 ) { percent = 100; }
+
+		var newval = Math.round( (percent / 100) * (max - min) ) + min;
+		if ( newval < min ) { newval = min; }
+		if ( newval > max ) { newval = max; }
+
+		//flip the stack of the bg colors
+		if ( percent > 60 && cType === "select" ) {
+
+		}
+		this.handle.css("left", percent + "%");
+		this.handle.attr({
+				"aria-valuenow": (cType === "input") ? newval : control.find("option").eq(newval).attr("value"),
+				"aria-valuetext": (cType === "input") ? newval : control.find("option").eq(newval).text(),
+				title: newval
+			});
+
+		// add/remove classes for flip toggle switch
+		if ( cType === "select" ) {
+			if ( newval === 0 ) {
+				this.slider.addClass("ui-slider-switch-a")
+					.removeClass("ui-slider-switch-b");
+			} else {
+				this.slider.addClass("ui-slider-switch-b")
+					.removeClass("ui-slider-switch-a");
+			}
+		}
+
+		if(!preventInputUpdate){
+			// update control's value
+			if ( cType === "input" ) {
+				control.val(newval);
+			} else {
+				control[ 0 ].selectedIndex = newval;
+			}
+			if (!isfromControl) { control.trigger("change"); }
+		}
+	},
+
+	enable: function(){
+		this.element.attr("disabled", false);
+		this.slider.removeClass("ui-disabled").attr("aria-disabled", false);
+		return this._setOption("disabled", false);
+	},
+
+	disable: function(){
+		this.element.attr("disabled", true);
+		this.slider.addClass("ui-disabled").attr("aria-disabled", true);
+		return this._setOption("disabled", true);
+	}
+
+});
+})( jQuery );
+
+/*
+* jQuery Mobile Framework : "collapsible" plugin
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/ 
+(function($, undefined ) {
+$.widget( "mobile.collapsible", $.mobile.widget, {
+	options: {
+		expandCueText: ' click to expand contents',
+		collapseCueText: ' click to collapse contents',
+		collapsed: false,
+		heading: '>:header,>legend',
+		theme: null,
+		iconTheme: 'd'
+	},
+	_create: function(){
+
+		var $el = this.element,
+			o = this.options,
+			collapsibleContain = $el.addClass('ui-collapsible-contain'),
+			collapsibleHeading = $el.find(o.heading).eq(0),
+			collapsibleContent = collapsibleContain.wrapInner('<div class="ui-collapsible-content"></div>').find('.ui-collapsible-content'),
+			collapsibleParent = $el.closest( ":jqmData(role='collapsible-set')" ).addClass('ui-collapsible-set');				
+		
+		//replace collapsibleHeading if it's a legend	
+		if(collapsibleHeading.is('legend')){
+			collapsibleHeading = $('<div role="heading">'+ collapsibleHeading.html() +'</div>').insertBefore(collapsibleHeading);
+			collapsibleHeading.next().remove();
+		}	
+		
+		//drop heading in before content
+		collapsibleHeading.insertBefore(collapsibleContent);
+		
+		//modify markup & attributes
+		collapsibleHeading.addClass('ui-collapsible-heading')
+			.append('<span class="ui-collapsible-heading-status"></span>')
+			.wrapInner('<a href="#" class="ui-collapsible-heading-toggle"></a>')
+			.find('a:eq(0)')
+			.buttonMarkup({
+				shadow: !!!collapsibleParent.length,
+				corners:false,
+				iconPos: 'left',
+				icon: 'plus',
+				theme: o.theme
+			})
+			.find('.ui-icon')
+			.removeAttr('class')
+			.buttonMarkup({
+				shadow: true,
+				corners:true,
+				iconPos: 'notext',
+				icon: 'plus',
+				theme: o.iconTheme
+			});
+			
+			if( !collapsibleParent.length ){
+				collapsibleHeading
+					.find('a:eq(0)')	
+					.addClass('ui-corner-all')
+						.find('.ui-btn-inner')
+						.addClass('ui-corner-all');
+			}
+			else {
+				if( collapsibleContain.jqmData('collapsible-last') ){
+					collapsibleHeading
+						.find('a:eq(0), .ui-btn-inner')	
+							.addClass('ui-corner-bottom');
+				}					
+			}
+			
+		
+		//events
+		collapsibleContain	
+			.bind('collapse', function(event){
+				if( !event.isDefaultPrevented() ){
+					event.preventDefault();
+					collapsibleHeading
+						.addClass('ui-collapsible-heading-collapsed')
+						.find('.ui-collapsible-heading-status').text(o.expandCueText);
+					
+					collapsibleHeading.find('.ui-icon').removeClass('ui-icon-minus').addClass('ui-icon-plus');	
+					collapsibleContent.addClass('ui-collapsible-content-collapsed').attr('aria-hidden',true);
+					
+					if( collapsibleContain.jqmData('collapsible-last') ){
+						collapsibleHeading
+							.find('a:eq(0), .ui-btn-inner')
+							.addClass('ui-corner-bottom');
+					}
+				}						
+				
+			})
+			.bind('expand', function(event){
+				if( !event.isDefaultPrevented() ){
+					event.preventDefault();
+					collapsibleHeading
+						.removeClass('ui-collapsible-heading-collapsed')
+						.find('.ui-collapsible-heading-status').text(o.collapseCueText);
+					
+					collapsibleHeading.find('.ui-icon').removeClass('ui-icon-plus').addClass('ui-icon-minus');	
+					collapsibleContent.removeClass('ui-collapsible-content-collapsed').attr('aria-hidden',false);
+					
+					if( collapsibleContain.jqmData('collapsible-last') ){
+						collapsibleHeading
+							.find('a:eq(0), .ui-btn-inner')
+							.removeClass('ui-corner-bottom');
+					}
+					
+				}
+			})
+			.trigger(o.collapsed ? 'collapse' : 'expand');
+			
+		
+		//close others in a set
+		if( collapsibleParent.length && !collapsibleParent.jqmData("collapsiblebound") ){
+			collapsibleParent
+				.jqmData("collapsiblebound", true)
+				.bind("expand", function( event ){
+					$(this).find( ".ui-collapsible-contain" )
+						.not( $(event.target).closest( ".ui-collapsible-contain" ) )
+						.not( "> .ui-collapsible-contain .ui-collapsible-contain" )
+						.trigger( "collapse" );
+				});
+			var set = collapsibleParent.find( ":jqmData(role=collapsible)" )
+					
+			set.first()
+				.find('a:eq(0)')	
+				.addClass('ui-corner-top')
+					.find('.ui-btn-inner')
+					.addClass('ui-corner-top');
+					
+			set.last().jqmData('collapsible-last', true)	
+		}
+					
+		collapsibleHeading
+			.bind("vclick", function(e){ 
+					if( collapsibleHeading.is('.ui-collapsible-heading-collapsed') ){
+						collapsibleContain.trigger('expand'); 
+					}	
+					else {
+						collapsibleContain.trigger('collapse'); 
+					}
+					e.preventDefault();
+				});
+	}
+});
+})( jQuery );/*
+* jQuery Mobile Framework: "controlgroup" plugin - corner-rounding for groups of buttons, checks, radios, etc
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+$.fn.controlgroup = function(options){
+		
+	return this.each(function(){
+		var o = $.extend({
+			direction: $( this ).jqmData( "type" ) || "vertical",
+			shadow: false
+		},options);
+		var groupheading = $(this).find('>legend'),
+			flCorners = o.direction == 'horizontal' ? ['ui-corner-left', 'ui-corner-right'] : ['ui-corner-top', 'ui-corner-bottom'],
+			type = $(this).find('input:eq(0)').attr('type');
+		
+		//replace legend with more stylable replacement div	
+		if( groupheading.length ){
+			$(this).wrapInner('<div class="ui-controlgroup-controls"></div>');	
+			$('<div role="heading" class="ui-controlgroup-label">'+ groupheading.html() +'</div>').insertBefore( $(this).children(0) );	
+			groupheading.remove();	
+		}
+
+		$(this).addClass('ui-corner-all ui-controlgroup ui-controlgroup-'+o.direction);
+		
+		function flipClasses(els){
+			els
+				.removeClass('ui-btn-corner-all ui-shadow')
+				.eq(0).addClass(flCorners[0])
+				.end()
+				.filter(':last').addClass(flCorners[1]).addClass('ui-controlgroup-last');
+		}
+		flipClasses($(this).find('.ui-btn'));
+		flipClasses($(this).find('.ui-btn-inner'));
+		if(o.shadow){
+			$(this).addClass('ui-shadow');
+		}
+	});	
+};
+})(jQuery);/*
+* jQuery Mobile Framework : "fieldcontain" plugin - simple class additions to make form row separators
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+$.fn.fieldcontain = function(options){
+	return this.addClass('ui-field-contain ui-body ui-br');
+};
+})(jQuery);/*
+* jQuery Mobile Framework : "listview" plugin
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+
+$.widget( "mobile.listview", $.mobile.widget, {
+	options: {
+		theme: "c",
+		countTheme: "c",
+		headerTheme: "b",
+		dividerTheme: "b",
+		splitIcon: "arrow-r",
+		splitTheme: "b",
+		inset: false
+	},
+	
+	_create: function() {
+		var $list = this.element,
+			o = this.options;
+
+		// create listview markup 
+		$list
+			.addClass( "ui-listview" );
+		
+		if ( o.inset ) {
+			$list.addClass( "ui-listview-inset ui-corner-all ui-shadow" );
+		}
+
+		this._itemApply( $list, $list );
+		
+		this.refresh( true );
+
+	},
+
+	_itemApply: function( $list, item ) {
+		// TODO class has to be defined in markup
+		item.find( ".ui-li-count" )
+			.addClass( "ui-btn-up-" + ($list.jqmData( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" );
+
+		item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" );
+
+		item.find( "p, dl" ).addClass( "ui-li-desc" );
+
+		$list.find( "li" ).find( ">img:eq(0), >:first>img:eq(0)" ).addClass( "ui-li-thumb" ).each(function() {
+			$( this ).closest( "li" ).addClass( $(this).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
+		});
+
+		var aside = item.find( ".ui-li-aside" );
+
+		if ( aside.length ) {
+            aside.each(function(i, el) {
+			    $(el).prependTo( $(el).parent() ); //shift aside to front for css float
+            });
+		}
+
+		if ( $.support.cssPseudoElement || !$.nodeName( item[0], "ol" ) ) {
+			return;
+		}
+	},
+	
+	_removeCorners: function(li){
+		li
+			.add( li.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb") )
+			.removeClass( "ui-corner-top ui-corner-bottom ui-corner-br ui-corner-bl ui-corner-tr ui-corner-tl" );
+	},
+	
+	refresh: function( create ) {
+		this._createSubPages();
+		
+		var o = this.options,
+			$list = this.element,
+			self = this,
+			dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme,
+			li = $list.children( "li" ),
+			counter = $.support.cssPseudoElement || !$.nodeName( $list[0], "ol" ) ? 0 : 1;
+
+		if ( counter ) {
+			$list.find( ".ui-li-dec" ).remove();
+		}
+
+		li.each(function( pos ) {
+			var item = $( this ),
+				itemClass = "ui-li";
+
+			// If we're creating the element, we update it regardless
+			if ( !create && item.hasClass( "ui-li" ) ) {
+				return;
+			}
+
+			var itemTheme = item.jqmData("theme") || o.theme;
+
+			var a = item.find( ">a" );
+				
+			if ( a.length ) {	
+				var icon = item.jqmData("icon");
+				
+				item
+					.buttonMarkup({
+						wrapperEls: "div",
+						shadow: false,
+						corners: false,
+						iconpos: "right",
+						icon: a.length > 1 || icon === false ? false : icon || "arrow-r",
+						theme: itemTheme
+					});
+
+				a.first().addClass( "ui-link-inherit" );
+
+				if ( a.length > 1 ) {
+					itemClass += " ui-li-has-alt";
+
+					var last = a.last(),
+						splittheme = $list.jqmData( "splittheme" ) || last.jqmData( "theme" ) || o.splitTheme;
+					
+					last
+						.appendTo(item)
+						.attr( "title", last.text() )
+						.addClass( "ui-li-link-alt" )
+						.empty()
+						.buttonMarkup({
+							shadow: false,
+							corners: false,
+							theme: itemTheme,
+							icon: false,
+							iconpos: false
+						})
+						.find( ".ui-btn-inner" )
+							.append( $( "<span>" ).buttonMarkup({
+								shadow: true,
+								corners: true,
+								theme: splittheme,
+								iconpos: "notext",
+								icon: $list.jqmData( "spliticon" ) || last.jqmData( "icon" ) ||  o.splitIcon
+							} ) );
+				}
+
+			} else if ( item.jqmData( "role" ) === "list-divider" ) {
+				itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
+				item.attr( "role", "heading" );
+
+				//reset counter when a divider heading is encountered
+				if ( counter ) {
+					counter = 1;
+				}
+
+			} else {
+				itemClass += " ui-li-static ui-body-" + itemTheme;
+			}
+			
+			
+			if( o.inset ){	
+				if ( pos === 0 ) {
+						itemClass += " ui-corner-top";
+	
+						item
+							.add( item.find( ".ui-btn-inner" ) )
+							.find( ".ui-li-link-alt" )
+								.addClass( "ui-corner-tr" )
+							.end()
+							.find( ".ui-li-thumb" )
+								.addClass( "ui-corner-tl" );
+						if(item.next().next().length){
+							self._removeCorners( item.next() );		
+						}
+	
+				}
+				if ( pos === li.length - 1 ) {
+						itemClass += " ui-corner-bottom";
+	
+						item
+							.add( item.find( ".ui-btn-inner" ) )
+							.find( ".ui-li-link-alt" )
+								.addClass( "ui-corner-br" )
+							.end()
+							.find( ".ui-li-thumb" )
+								.addClass( "ui-corner-bl" );
+						
+						if(item.prev().prev().length){
+							self._removeCorners( item.prev() );		
+						}	
+				}
+			}
+
+
+			if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
+			
+				var countParent = item.is(".ui-li-static:first") ? item : item.find( ".ui-link-inherit" );
+				
+				countParent
+					.addClass( "ui-li-jsnumbering" )
+					.prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" );
+			}
+
+			item.add( item.find( ".ui-btn-inner" ) ).addClass( itemClass );
+
+			if ( !create ) {
+				self._itemApply( $list, item );
+			}
+		});
+	},
+	
+	//create a string for ID/subpage url creation
+	_idStringEscape: function( str ){
+		return str.replace(/[^a-zA-Z0-9]/g, '-');
+	},
+	
+	_createSubPages: function() {
+		var parentList = this.element,
+			parentPage = parentList.closest( ".ui-page" ),
+			parentId = parentPage.jqmData( "url" ),
+			o = this.options,
+			self = this,
+			persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" );
+
+		$( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function( i ) {
+			var list = $( this ),
+				parent = list.parent(),
+				nodeEls = $( list.prevAll().toArray().reverse() ),
+				nodeEls = nodeEls.length ? nodeEls : $( "<span>" + $.trim(parent.contents()[ 0 ].nodeValue) + "</span>" ),
+				title = nodeEls.first().text(),//url limits to first 30 chars of text
+				id = parentId + "&" + $.mobile.subPageUrlKey + "=" + self._idStringEscape(title + " " + i),
+				theme = list.jqmData( "theme" ) || o.theme,
+				countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme,
+				newPage = list.wrap( "<div data-" + $.mobile.ns + "role='page'><div data-" + $.mobile.ns + "role='content'></div></div>" )
+							.parent()
+								.before( "<div data-" + $.mobile.ns + "role='header' data-" + $.mobile.ns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" )
+								.after( persistentFooterID ? $( "<div data-" + $.mobile.ns + "role='footer'  data-" + $.mobile.ns + "id='"+ persistentFooterID +"'>") : "" )
+								.parent()
+									.attr( "data-" + $.mobile.ns + "url", id )
+									.attr( "data-" + $.mobile.ns + "theme", theme )
+									.attr( "data-" + $.mobile.ns + "count-theme", countTheme )
+									.appendTo( $.mobile.pageContainer );
+
+				newPage.page();		
+			var anchor = parent.find('a:first');
+			if (!anchor.length) {
+				anchor = $("<a></a>").html( nodeEls || title ).prependTo(parent.empty());
+			}
+			anchor.attr('href','#' + id);
+		}).listview();
+	}
+});
+
+})( jQuery );
+/*
+* jQuery Mobile Framework : "listview" filter extension
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+
+$.mobile.listview.prototype.options.filter = false;
+$.mobile.listview.prototype.options.filterPlaceholder = "Filter items...";
+
+$( ":jqmData(role='listview')" ).live( "listviewcreate", function() {
+	var list = $( this ),
+		listview = list.data( "listview" );
+
+	if ( !listview.options.filter ) {
+		return;
+	}
+
+	var wrapper = $( "<form>", { "class": "ui-listview-filter ui-bar-c", "role": "search" } ),
+
+		search = $( "<input>", {
+				placeholder: listview.options.filterPlaceholder
+			})
+			.attr( "data-" + $.mobile.ns + "type", "search" )
+			.bind( "keyup change", function() {
+				var val = this.value.toLowerCase(),
+						listItems = list.children();
+				listItems.show();
+				if ( val ) {
+					// This handles hiding regular rows without the text we search for
+					// and any list dividers without regular rows shown under it
+					var childItems = false,
+							item;
+
+					for (var i = listItems.length; i >= 0; i--) {
+						item = $(listItems[i]);
+						if (item.is("li:jqmData(role=list-divider)")) {
+							if (!childItems) {
+								item.hide();
+							}
+							// New bucket!
+							childItems = false;
+						} else if (item.text().toLowerCase().indexOf( val ) === -1) {
+							item.hide();
+						} else {
+							// There's a shown item in the bucket
+							childItems = true;
+						}
+					}
+				}
+			})
+			.appendTo( wrapper )
+			.textinput();
+
+	if ($( this ).jqmData( "inset" ) ) {
+		wrapper.addClass( "ui-listview-filter-inset" );
+	}
+
+	wrapper.insertBefore( list );
+});
+
+})( jQuery );
+/*
+* jQuery Mobile Framework : "dialog" plugin.
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* Note: Code is in draft form and is subject to change
+*/
+(function($, undefined ) {
+$.widget( "mobile.dialog", $.mobile.widget, {
+	options: {
+		closeBtnText: "Close"
+	},
+	_create: function(){
+		var self = this,
+			$el = self.element;
+		
+		/* class the markup for dialog styling */	
+		this.element			
+			//add ARIA role
+			.attr("role","dialog")
+			.addClass('ui-page ui-dialog ui-body-a')
+			.find( ":jqmData(role=header)" )
+			.addClass('ui-corner-top ui-overlay-shadow')
+				.prepend( "<a href='#' data-" + $.mobile.ns + "icon='delete' data-" + $.mobile.ns + "rel='back' data-" + $.mobile.ns + "iconpos='notext'>"+ this.options.closeBtnText +"</a>" )
+			.end()
+			.find('.ui-content:not([class*="ui-body-"])')
+				.addClass('ui-body-c')
+			.end()
+			.find( ".ui-content,:jqmData(role='footer')" )
+				.last()
+				.addClass('ui-corner-bottom ui-overlay-shadow');
+		
+		/* bind events 
+			- clicks and submits should use the closing transition that the dialog opened with
+			  unless a data-transition is specified on the link/form
+			- if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally
+		*/
+		this.element		
+			.bind( "vclick submit", function(e){
+				var $targetel;
+				if( e.type == "vclick" ){
+					$targetel = $(e.target).closest("a");
+				}
+				else{
+					$targetel = $(e.target).closest("form");
+				}
+				
+				if( $targetel.length && !$targetel.jqmData("transition") ){
+					$targetel
+						.attr("data-" + $.mobile.ns + "transition", $.mobile.urlHistory.getActive().transition )
+						.attr("data-" + $.mobile.ns + "direction", "reverse");
+				}
+			});
+
+	},
+	
+	//close method goes back in history
+	close: function(){
+		window.history.back();
+	}
+});
+})( jQuery );/*
+* jQuery Mobile Framework : "navbar" plugin
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+(function($, undefined ) {
+$.widget( "mobile.navbar", $.mobile.widget, {
+	options: {
+		iconpos: 'top',
+		grid: null
+	},
+	_create: function(){
+		var $navbar = this.element,
+			$navbtns = $navbar.find("a"),
+			iconpos = $navbtns.filter( ":jqmData(icon)").length ? this.options.iconpos : undefined;
+		
+		$navbar
+			.addClass('ui-navbar')
+			.attr("role","navigation")
+			.find("ul")
+				.grid({grid: this.options.grid });		
+		
+		if( !iconpos ){ 
+			$navbar.addClass("ui-navbar-noicons");
+		}
+		
+		$navbtns
+			.buttonMarkup({
+				corners:	false, 
+				shadow:		false, 
+				iconpos:	iconpos
+			});
+		
+		$navbar.delegate("a", "vclick",function(event){
+			$navbtns.not( ".ui-state-persist" ).removeClass( $.mobile.activeBtnClass );
+			$( this ).addClass( $.mobile.activeBtnClass );
+		});	
+	}
+});
+})( jQuery );
+/*
+* jQuery Mobile Framework : plugin for creating CSS grids
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/ 
+(function($, undefined ) {
+$.fn.grid = function(options){
+	return this.each(function(){
+		var o = $.extend({
+			grid: null
+		},options);
+	
+			
+		var $kids = $(this).children(),
+			gridCols = {solo:1, a:2, b:3, c:4, d:5},
+			grid = o.grid,
+			iterator;
+			
+			if( !grid ){
+				if( $kids.length <= 5 ){
+					for(var letter in gridCols){
+						if(gridCols[letter] == $kids.length){ grid = letter; }
+					}
+				}
+				else{
+					grid = 'a';
+				}
+			}
+			iterator = gridCols[grid];
+			
+		$(this).addClass('ui-grid-' + grid);
+	
+		$kids.filter(':nth-child(' + iterator + 'n+1)').addClass('ui-block-a');
+		if(iterator > 1){	
+			$kids.filter(':nth-child(' + iterator + 'n+2)').addClass('ui-block-b');
+		}	
+		if(iterator > 2){	
+			$kids.filter(':nth-child(3n+3)').addClass('ui-block-c');
+		}	
+		if(iterator > 3){	
+			$kids.filter(':nth-child(4n+4)').addClass('ui-block-d');
+		}	
+		if(iterator > 4){	
+			$kids.filter(':nth-child(5n+5)').addClass('ui-block-e');
+		}
+				
+	});	
+};
+})(jQuery);/*!
+ * jQuery Mobile v@VERSION
+ * http://jquerymobile.com/
+ *
+ * Copyright 2010, jQuery Project
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ */
+
+(function( $, window, undefined ) {
+	var	$html = $( "html" ),
+			$head = $( "head" ),
+			$window = $( window );
+
+ 	//trigger mobileinit event - useful hook for configuring $.mobile settings before they're used
+	$( window.document ).trigger( "mobileinit" );
+
+	//support conditions
+	//if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
+	//otherwise, proceed with the enhancements
+	if ( !$.mobile.gradeA() ) {
+		return;
+	}
+
+	//add mobile, initial load "rendering" classes to docEl
+	$html.addClass( "ui-mobile ui-mobile-rendering" );
+
+	//define & prepend meta viewport tag, if content is defined
+	//NOTE: this is now deprecated. We recommend placing the meta viewport element in
+	//the markup from the start.
+	$.mobile.metaViewportContent && !$head.find( "meta[name='viewport']" ).length ? $( "<meta>", { name: "viewport", content: $.mobile.metaViewportContent}).prependTo( $head ) : undefined;
+
+	//loading div which appears during Ajax requests
+	//will not appear if $.mobile.loadingMessage is false
+	var $loader = $.mobile.loadingMessage ?		$( "<div class='ui-loader ui-body-a ui-corner-all'>" + "<span class='ui-icon ui-icon-loading spin'></span>" + "<h1>" + $.mobile.loadingMessage + "</h1>" + "</div>" )	: undefined;
+
+	if(typeof $loader === "undefined"){
+		alert($.mobile.loadingMessage);
+	}
+
+	$.extend($.mobile, {
+		// turn on/off page loading message.
+		pageLoading: function ( done ) {
+			if ( done ) {
+				$html.removeClass( "ui-loading" );
+			} else {
+				if( $.mobile.loadingMessage ){
+					var activeBtn = $( "." + $.mobile.activeBtnClass ).first();
+
+
+					if(typeof $loader === "undefined"){
+						 alert($.mobile.loadingMessage);
+					}
+
+					$loader
+						.appendTo( $.mobile.pageContainer )
+						//position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top
+						.css( {
+							top: $.support.scrollTop && $(window).scrollTop() + $(window).height() / 2 ||
+							activeBtn.length && activeBtn.offset().top || 100
+						} );
+				}
+
+				$html.addClass( "ui-loading" );
+			}
+		},
+
+		// find and enhance the pages in the dom and transition to the first page.
+		initializePage: function(){
+			//find present pages
+			var $pages = $( ":jqmData(role='page')" );
+
+			//add dialogs, set data-url attrs
+			$pages.add( ":jqmData(role='dialog')" ).each(function(){
+				var $this = $(this);
+
+				// unless the data url is already set set it to the id
+				if( !$this.jqmData('url') ){
+					$this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) );
+				}
+			});
+
+			//define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
+			$.mobile.firstPage = $pages.first();
+
+			//define page container
+			$.mobile.pageContainer = $pages.first().parent().addClass( "ui-mobile-viewport" );
+
+			//cue page loading message
+			$.mobile.pageLoading();
+
+			// if hashchange listening is disabled or there's no hash deeplink, change to the first page in the DOM
+			if( !$.mobile.hashListeningEnabled || !$.mobile.path.stripHash( location.hash ) ){
+				$.mobile.changePage( $.mobile.firstPage, false, true, false, true );
+			}
+			// otherwise, trigger a hashchange to load a deeplink
+			else {
+				$window.trigger( "hashchange", [ true ] );
+			}
+		}
+	});
+
+	//dom-ready inits
+	$( $.mobile.initializePage );
+
+	//window load event
+	//hide iOS browser chrome on load
+	$window.load( $.mobile.silentScroll );
+})( jQuery, this );
+

symlink:b/labs/about.php (new)
--- /dev/null
+++ b/labs/about.php
@@ -1,1 +1,1 @@
-
+../about.php

--- /dev/null
+++ b/labs/busstopdensity.php
@@ -1,1 +1,78 @@
+<?php
+include ('../include/common.inc.php');
+//include_header("Bus Stop Density", "busstopdensity")
+?>
 
+<style type="text/css">
+
+			#map_container{
+				width:100%;
+				height:100%;
+			}
+			#status, #legend {
+				background-color: #eeeeee;
+				padding: 3px 5px 3px 5px;
+				opacity: .9;
+			}
+		</style>
+	
+		<div id="map_container"></div>
+			<div id="status">
+			Status:&nbsp; <span id="log"> <img alt="progess bar" src="progress_bar.gif" width="150" height="16"/></span>
+		</div>
+		<script type="text/javascript" src="http://www.google.com/jsapi?autoload={%22modules%22:[{%22name%22:%22maps%22,version:3,other_params:%22sensor=false%22},{%22name%22:%22jquery%22,%22version%22:%221.4.2%22}]}"></script>
+		<script type="text/javascript">
+		//<![CDATA[
+		//Google Map API v3
+		
+		var googleMap = null;
+		var previousPos = null;
+
+		$(function($){//Called when page is loaded
+			googleMap = new google.maps.Map(document.getElementById("map_container"), {
+				zoom: 17, 
+				minZoom: 12, 
+				center: new google.maps.LatLng(-35.25,149.125), 
+				mapTypeId: google.maps.MapTypeId.SATELLITE});
+			//Set status bar
+			googleMap.controls[google.maps.ControlPosition.TOP_LEFT].push($("#status").get(0));
+			//Set legend
+			googleMap.controls[google.maps.ControlPosition.BOTTOM_LEFT].push($("#legend").get(0));
+
+			google.maps.event.addListener(googleMap, "zoom_changed", function(){
+				google.maps.event.trigger(googleMap, "mousemove", previousPos);
+			});//onzoomend
+				
+			//Add a listener when mouse moves
+			google.maps.event.addListener(googleMap, "mousemove", function(event){
+				var latLng = event.latLng;
+				var xy = googleMap.getProjection().fromLatLngToPoint(latLng);
+				var ratio = Math.pow(2,googleMap.getZoom());
+				$("#log").html("Zoom:" + googleMap.getZoom() + " WGS84:(" + latLng.lat().toFixed(5) + ", " + latLng.lng().toFixed(5) + ") Px:(" + Math.floor(xy.x * ratio)  + "," + Math.floor(xy.y *ratio) + ")");
+				previousPos = event;
+			});//onmouseover
+
+			//Add a listener when mouse leaves the map area
+			google.maps.event.addListener(googleMap, "mouseout", function(event){
+				$("#log").html("");
+			});//onmouseout
+
+			//Add tile overlay
+			var myOverlay = new google.maps.ImageMapType({
+				getTileUrl: function(coord, zoom) {
+					return 'busstopdensity.tile.php?x=' + coord.x + '&y=' + coord.y + '&zoom=' +zoom;
+				},
+				tileSize: new google.maps.Size(256, 256),
+				isPng: true,
+				opacity:1.0
+			});
+			googleMap.overlayMapTypes.insertAt(0, myOverlay);
+			$("#log").html("Map loaded!");
+		});//onload
+		//]]>
+		</script>
+<?php
+include_footer()
+?>
+        
+

--- /dev/null
+++ b/labs/busstopdensity.tile.php
@@ -1,1 +1,124 @@
+<?php
+include ('../include/common.inc.php');
+$debugOkay = Array();
 
+/*
+*DISCLAIMER
+*  http://blog.gmapify.fr/create-beautiful-tiled-heat-maps-with-php-and-gd
+*THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES *OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, *INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*	@author: Olivier G. <olbibigo_AT_gmail_DOT_com>
+*	@version: 1.0
+*	@history:
+*		1.0	creation
+*/
+	set_time_limit(120);//2mn
+	ini_set('memory_limit', '256M');
+error_reporting(E_ALL ^ E_DEPRECATED);
+	require_once ('lib/GoogleMapUtility.php');
+	require_once ('lib/HeatMap.php');
+
+	//Root folder to store generated tiles
+	define('TILE_DIR', 'tiles/');
+	//Covered geographic areas
+	define('MIN_LAT', -35.48);
+	define('MAX_LAT', -35.15);
+	define('MIN_LNG', 148.98);
+	define('MAX_LNG', 149.25);
+	define('TILE_SIZE_FACTOR', 0.5);
+	define('SPOT_RADIUS', 30);
+	define('SPOT_DIMMING_LEVEL', 50);
+	
+	//Input parameters
+	if(isset($_GET['x']))
+		$X = (int)$_GET['x'];
+	else
+		exit("x missing");
+	if(isset($_GET['y']))
+		$Y = (int)$_GET['y'];
+	else
+		exit("y missing");
+	if(isset($_GET['zoom']))
+		$zoom = (int)$_GET['zoom'];
+	else
+		exit("zoom missing");
+if ($zoom < 12) { //enforce minimum zoom
+			header('Content-type: image/png');
+			echo file_get_contents(TILE_DIR.'empty.png');
+}
+	$dir = TILE_DIR.$zoom;
+	$tilename = $dir.'/'.$X.'_'.$Y.'.png';
+	//HTTP headers  (data type and caching rule)
+	header("Cache-Control: must-revalidate");
+	header("Expires: " . gmdate("D, d M Y H:i:s", time() + 86400) . " GMT");
+	if(!file_exists($tilename)){
+		$rect = GoogleMapUtility::getTileRect($X, $Y, $zoom);
+		//A tile can contain part of a spot with center in an adjacent tile (overlaps).
+		//Knowing the spot radius (in pixels) and zoom level, a smart way to process tiles would be to compute the box (in decimal degrees) containing only spots that can be drawn on current tile. We choose a simpler solution by increeasing  geo bounds by 2*TILE_SIZE_FACTOR whatever the zoom level and spot radius.
+		$extend_X = $rect->width * TILE_SIZE_FACTOR;//in decimal degrees
+		$extend_Y = $rect->height * TILE_SIZE_FACTOR;//in decimal degrees
+		$swlat = $rect->y - $extend_Y;
+		$swlng = $rect->x - $extend_X;
+		$nelat = $swlat + $rect->height + 2 * $extend_Y;
+		$nelng = $swlng + $rect->width + 2 * $extend_X;
+
+		if( ($nelat <= MIN_LAT) || ($swlat >= MAX_LAT) || ($nelng <= MIN_LNG) || ($swlng >= MAX_LNG)){
+			//No geodata so return generic empty tile
+			echo file_get_contents(TILE_DIR.'empty.png');
+			exit();
+		}
+
+		//Get McDonald's spots
+		$spots = fGetPOI('Select * from stops where
+				(stop_lon > '.$swlng.' AND stop_lon < '.$nelng.')
+			AND (stop_lat < '.$nelat.' AND stop_lat > '.$swlat.')', $im, $X, $Y, $zoom, SPOT_RADIUS);
+
+		
+		if(empty($spots)){
+			//No geodata so return generic empty tile
+			header('Content-type: image/png');
+			echo file_get_contents(TILE_DIR.'empty.png');
+		}else{
+			if(!file_exists($dir)){
+				mkdir($dir, 0705);
+			}
+			//All the magics is in HeatMap class :)
+			$im = HeatMap::createImage($spots, GoogleMapUtility::TILE_SIZE, GoogleMapUtility::TILE_SIZE, heatMap::$WITH_ALPHA, SPOT_RADIUS, SPOT_DIMMING_LEVEL, HeatMap::$GRADIENT_FIRE);
+			//Store tile for reuse and output it
+			header('content-type:image/png;');
+			imagepng($im, $tilename);
+			echo file_get_contents($tilename);
+			imagedestroy($im);
+			unset($im);
+		}
+	}else{
+		//Output stored tile
+		header('content-type:image/png;');
+		echo file_get_contents($tilename);
+	}
+	/////////////
+	//Functions//
+	/////////////
+	function fGetPOI($query, &$im, $X, $Y, $zoom, $offset){
+            global $conn;
+		$nbPOIInsideTile = 0;
+
+	$result = pg_query($conn, $query);
+        $spots = Array();
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	foreach( pg_fetch_all($result) as $row){
+				$point = GoogleMapUtility::getOffsetPixelCoords($row['stop_lat'], $row['stop_lon'], $zoom, $X, $Y);
+				//Count result only in the tile
+				if( ($point->x > -$offset) && ($point->x < (GoogleMapUtility::TILE_SIZE+$offset)) && ($point->y > -$offset) && ($point->y < (GoogleMapUtility::TILE_SIZE+$offset))){
+					$spots[] = new HeatMapPoint($point->x, $point->y);
+				}
+				
+			}//while
+		return $spots;
+	}//fAddPOI
+?>
+
+

symlink:b/labs/css (new)
--- /dev/null
+++ b/labs/css
@@ -1,1 +1,1 @@
-
+../css/

symlink:b/labs/feedback.php (new)
--- /dev/null
+++ b/labs/feedback.php
@@ -1,1 +1,1 @@
-
+../feedback.php

 Binary files /dev/null and b/labs/gradient/classic.png differ
 Binary files /dev/null and b/labs/gradient/fire.png differ
 Binary files /dev/null and b/labs/gradient/pgaitch.png differ
file:b/labs/index.php (new)
--- /dev/null
+++ b/labs/index.php
@@ -1,1 +1,20 @@
+<?php
+include ('../include/common.inc.php');
+include_header("Busness R&amp;D", "index")
+?>
+	    <ul data-role="listview" data-theme="e" data-groupingtheme="e">
+		<li data-role="list-divider" > Experimental Features </li>
+		<li><a href="mywaybalance.php"><h3>MyWay Balance for mobile</h3>
+		<p>Mobile viewer for MyWay balance. Warning! No HTTPS security.</p></a></li>
+		<li><a href="networkstats.php"><h3>Route Statistics</h3>
+		<p>Analysis of route timing points</p></a></li>
+		<li><a href="busstopdensity.php"><h3>Bus Stop Density Map</h3>
+		<p>Analysis of bus stop coverage</p></a></li>
+		<li>More coming soon!</li>
+            </ul>
+	    </div>
+<?php
+include_footer()
+?>
+        
 

symlink:b/labs/js (new)
--- /dev/null
+++ b/labs/js
@@ -1,1 +1,1 @@
-
+../js

symlink:b/labs/lib (new)
--- /dev/null
+++ b/labs/lib
@@ -1,1 +1,1 @@
-
+../lib

--- a/labs/myway_api.json.php
+++ b/labs/myway_api.json.php
@@ -74,7 +74,7 @@
 }
 
 if (!isset($return['error'])) {
-	include_once ('simple_html_dom.php');
+	include_once ('lib/simple_html_dom.php');
 	$page = str_get_html($pageHTML);
 	$pageAlerts = $page->find(".smartCardAlert");
 	if (sizeof($pageAlerts) > 0) {

--- a/labs/mywaybalance.php
+++ b/labs/mywaybalance.php
@@ -1,22 +1,40 @@
 <?php
 include ('../include/common.inc.php');
-include_header("MyWay Balance", "mywayBalance", true, false, true);
+include_header("MyWay Balance", "mywayBalance", false, false, true);
+		echo '<div data-role="page"> 
+	<div data-role="header" data-position="inline">
+	<a href="' . $_SERVER["HTTP_REFERER"] . '" data-icon="arrow-l" data-rel="back" class="ui-btn-left">Back</a> 
+		<h1>MyWay Balance</h1>
+		<a href="mywaybalance.php?logout=yes" data-icon="delete" class="ui-btn-right">Logout</a>
+	</div><!-- /header -->
+        <a name="maincontent" id="maincontent"></a>
+        <div data-role="content"> ';
+	
 $return = Array();
+function logout() {
+	setcookie("card_number", "", time() - 60 * 60 * 24 * 100, "/");
+	setcookie("date", "", time() - 60 * 60 * 24 * 100, "/");
+	setcookie("secret_answer", "", time() - 60 * 60 * 24 * 100, "/");
+}
 function printBalance($cardNumber, $date, $pwrd)
 {
 	global $return;
-	$return = json_decode(getPage(curPageURL() . "/myway_api.json.php?card_number=$cardNumber&DOBday={$date[0]}&DOBmonth={$date[1]}&DOByear={$date[2]}&secret_answer=$pwrd"), true);
-    
-        if (isset($return['error'])) {
-            echo "<font color=red>" . var_dump($return['error']) . "</font>";
-        } else {
+	$return = json_decode(getPage(curPageURL() . "/myway_api.json.php?card_number=$cardNumber&DOBday={$date[0]}&DOBmonth={$date[1]}&DOByear={$date[2]}&secret_answer=$pwrd") , true);
+	if (isset($return['error'])) {
+		logout();
+		echo '<h3><font color="red">' . $return['error'][0] . "</font></h3>";
+	}
+	else {
 		echo "<h2>Balance: " . $return['myway_carddetails']['Card Balance'] . "</h2>";
 		echo '<ul data-role="listview" data-inset="true"><li data-role="list-divider"> Recent Transactions </li>';
+		$txCount=0;
 		foreach ($return['myway_transactions'] as $transaction) {
 			echo "<li><b>" . $transaction["Date / Time"] . "</b>";
-                        echo "<br><small>" . $transaction["TX Reference No / Type"]. "</small>";
-                        echo '<p class="ui-li-aside">'.$transaction["TX Amount"].'</p>';
+			echo "<br><small>" . $transaction["TX Reference No / Type"] . "</small>";
+			echo '<p class="ui-li-aside">' . $transaction["TX Amount"] . '</p>';
 			echo "</li>";
+			$txCount++;
+			if ($txCount > 10) break;
 		}
 		echo "</ul>";
 	}
@@ -25,12 +43,15 @@
 	$cardNumber = $_REQUEST['card_number'];
 	$date = explode("/", $_REQUEST['date']);
 	$pwrd = $_REQUEST['secret_answer'];
-	if ($_REQUEST['remember'] == true) {
-		$_COOKIE['card_number'] = $cardNumber;
-		$_COOKIE['date'] = $date;
-		$_COOKIE['secret_answer'] = $pwrd;
+	if ($_REQUEST['remember'] == "on") {
+		setcookie("card_number", $cardNumber, time() + 60 * 60 * 24 * 100, "/");
+		setcookie("date", $_REQUEST['date'], time() + 60 * 60 * 24 * 100, "/");
+		setcookie("secret_answer", $pwrd, time() + 60 * 60 * 24 * 100, "/");
 	}
 	printBalance($cardNumber, $date, $pwrd);
+}
+else if (isset($_REQUEST['logout'])) {
+	echo '<center><h3> Logged out of MyWay balance </h3><a href="/index.php">Back to main menu...</a><center>';
 }
 else if (isset($_COOKIE['card_number']) && isset($_COOKIE['date']) && isset($_COOKIE['secret_answer'])) {
 	$cardNumber = $_COOKIE['card_number'];

--- /dev/null
+++ b/labs/networkstats.php
@@ -1,1 +1,147 @@
+<?php
+include ('../include/common.inc.php');
+include_header("Route Statistics", "networkstats")
+?>
+<script type="text/javascript" src="js/flotr/lib/prototype-1.6.0.2.js"></script>
 
+		<!--[if IE]>
+
+			<script type="text/javascript" src="js/flotr/lib/excanvas.js"></script>
+
+			<script type="text/javascript" src="js/flotr/lib/base64.js"></script>
+
+		<![endif]-->
+
+		<script type="text/javascript" src="js/flotr/lib/canvas2image.js"></script>
+
+		<script type="text/javascript" src="js/flotr/lib/canvastext.js"></script>
+
+		<script type="text/javascript" src="js/flotr/flotr.debug-0.2.0-alpha_radar1.js"></script>
+		<form method="get" action="networkstats.php">
+			<select id="routeid" name="routeid">
+				<?php
+				foreach (getRoutes() as $route) {
+				echo "<option value=\"{$route['route_id']}\">{$route['route_short_name']} {$route['route_long_name']}</option>";
+				}
+				?>
+			</select>
+			<input type="submit" value="View"/>
+		</form>
+
+<?php
+// middle of graph = 6am
+$adjustFactor = 0;
+$route = getRoute($routeid);
+echo "<h1>{$route['route_short_name']} {$route['route_long_name']}</h1>";
+foreach (getRouteTrips($routeid) as $key => $trip) {
+	$dLabel[$key] = $trip['arrival_time'];
+	if ($key == 0) {
+		$time = strtotime($trip['arrival_time']);
+		$adjustFactor = (date("G", $time) * 3600);
+	}
+	$tripStops = viaPoints($trip['trip_id']);
+	foreach ($tripStops as $i => $stop) {
+		if ($key == 0) {
+			$dTicks[$i] = $stop['stop_name'];
+		}
+		$time = strtotime($stop['arrival_time']);
+		$d[$key][$i] = 	(date("G", $time) * 3600) + (date("i", $time) * 60) + date("s", $time) - $adjustFactor;
+
+	}
+}
+
+?>
+<div id="container" style="width:100%;height:900px;"></div>
+<script type="text/javascript">
+
+			/**
+
+			 * Wait till dom's finished loading.
+
+			 */
+
+			document.observe('dom:loaded', function(){
+
+				/**
+
+				 * Fill series d1 and d2.
+
+				 */
+<?php
+foreach ($d as $key => $dataseries) {
+	
+	echo "var d$key =[";
+	foreach ($dataseries as $i => $datapoint) {
+		echo "[$i, $datapoint],";
+	}
+	echo "];\n";
+}
+
+?>
+
+			    
+
+			    var f = Flotr.draw($('container'), 
+
+					[
+						<?php
+foreach ($d as $key => $dataseries) {
+	
+	echo '{data:d'.$key.", label:'{$dLabel[$key]}'".', radar:{fill:false}},'."\n";
+	
+}
+
+?>
+					 ],
+
+					{defaultType: 'radar',
+
+					 radarChartMode: true,
+
+					 HtmlText: false,
+
+					 fontSize: 9,
+
+					 xaxis:{
+
+						ticks: [
+							<?php
+foreach ($dTicks as $key => $tickName) {
+		echo '['.$key.', "'.$tickName.'"],';
+}
+
+?>
+							
+							]},
+
+					 mouse:{ // Setup point tracking
+
+						track: true,
+
+						lineColor: 'black',
+
+						relative: true,
+
+						sensibility: 70,
+
+						trackFormatter: function(obj){
+						var d = new Date();
+						d.setMinutes(0);
+						d.setHours(0);
+d.setTime(d.getTime() + Math.floor(obj.radarData*1000) + <?php echo $adjustFactor*1000 ?>);
+return d.getHours() +':'+ (d.getMinutes().toString().length == 1 ? '0'+ d.getMinutes():  d.getMinutes());
+}}});
+
+			});
+
+		</script>
+
+	    </div>
+	    
+
+
+<?php
+include_footer()
+?>
+        
+

 Binary files /dev/null and b/labs/tiles/empty.png differ
--- a/labs/tripPlannerTester.kml.php
+++ b/labs/tripPlannerTester.kml.php
@@ -8,6 +8,50 @@
 	}
 	else {
 		return (($pBegin - $pEnd) * (1 - ($pStep / $pMax))) + $pEnd;
+	}
+}
+require ("../lib/rolling-curl/RollingCurl.php");
+function processResult_cb($response, $info, $request)
+{
+	global $testRegions, $regionTimes,$csv,$kml, $latdeltasize,$londeltasize;
+	$md = $request->metadata;
+	$tripplan = json_decode($response);
+	$plans = Array();
+	//var_dump(Array($info, $request));
+	if (is_array($tripplan->plan->itineraries->itinerary)) {
+		foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {
+			$plans[floor($itinerary->duration / 60000) ] = $itinerary;
+		}
+	}
+	else {
+		$plans[floor($tripplan->plan->itineraries->itinerary->duration / 60000) ] = $tripplan->plan->itineraries->itinerary;
+	}
+	if ($csv) echo "{$md['i']},{$md['j']}," . min(array_keys($plans)) . ",$latdeltasize, $londeltasize,{$md['key']}\n";
+	if ($kml) {
+		$time = min(array_keys($plans));
+		$plan = "";
+		if (is_array($plans[min(array_keys($plans)) ]->legs->leg)) {
+			foreach ($plans[min(array_keys($plans)) ]->legs->leg as $legNumber => $leg) {
+				$plan.= processLeg($legNumber, $leg) . ",";
+			}
+		}
+		else {
+			$plan.= processLeg(0, $plans[min(array_keys($plans)) ]->legs->leg);
+		}
+		if (isset($tripplan->error) && $tripplan->error->id == 404) {
+			$time = 999;
+			$plan = "Trip not possible without excessive walking from nearest bus stop";
+		}
+		$testRegions[] = Array(
+			"lat" => $md['i'],
+			"lon" => $md['j'],
+			"time" => $time,
+			"latdeltasize" => $latdeltasize,
+			"londeltasize" => $londeltasize,
+			"regionname" => $md['key'],
+			"plan" => $plan . '<br/><a href="' . htmlspecialchars($md['url']) . '">original plan</a>'
+		);
+		$regionTimes[] = $time;
 	}
 }
 function Gradient($HexFrom, $HexTo, $ColorSteps)
@@ -49,18 +93,18 @@
 		//}
 		//$walkingstep.= floor($step->distance) . "m";
 		//return $walkingstep;
+		
 	}
 }
 $csv = false;
 $kml = true;
+$gearthcolors = false;
 if ($kml) {
-	//header('Content-Type: application/vnd.google-earth.kml+xml');
+	header('Content-Type: application/vnd.google-earth.kml+xml');
 	echo '<?xml version="1.0" encoding="UTF-8"?>
 <kml xmlns="http://www.opengis.net/kml/2.2"><Document>';
 }
 include ('../include/common.inc.php');
-//Test code to grab transit times
-// make sure to sleep(10);
 $boundingBoxes = Array(
 	"belconnen" => Array(
 		"startlat" => - 35.1928,
@@ -93,8 +137,8 @@
 		"finishlon" => 149.1243,
 	)
 );
-$latdeltasize = 0.01;
-$londeltasize = 0.01;
+$latdeltasize = 0.005;
+$londeltasize = 0.005;
 $from = "Wattle Street";
 $fromPlace = (startsWith($from, "-") ? $from : geocode($from, false));
 $startTime = "9:00 am";
@@ -103,82 +147,40 @@
 $regionTimes = Array();
 $testRegions = Array();
 $useragent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";
+if ($kml) echo "<name> $from at $startTime on $startDate </name>";
 if ($csv) echo "<pre>";
 if ($csv) echo "lat,lon,time,latdeltasize, londeltasize, region key name\n";
+$rc = new RollingCurl("processResult_cb");
+$rc->window_size = 2;
 foreach ($boundingBoxes as $key => $boundingBox) {
 	for ($i = $boundingBox['startlat']; $i >= $boundingBox['finishlat']; $i-= $latdeltasize) {
 		for ($j = $boundingBox['startlon']; $j <= $boundingBox['finishlon']; $j+= $londeltasize) {
 			$url = $otpAPIurl . "ws/plan?date=" . urlencode($startDate) . "&time=" . urlencode($startTime) . "&mode=TRANSIT%2CWALK&optimize=QUICK&maxWalkDistance=440&wheelchair=false&toPlace=" . $i . "," . $j . "&fromPlace=$fromPlace";
-			$ch = curl_init($url);
-			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-			curl_setopt($ch, CURLOPT_HEADER, 0);
-			curl_setopt($ch, CURLOPT_HTTPHEADER, array(
+			//debug($url);
+			$request = new RollingCurlRequest($url);
+			$request->headers = Array(
 				"Accept: application/json"
-			));
-			curl_setopt($ch, CURLOPT_TIMEOUT, 5);
-			$page = curl_exec($ch);
-			if (curl_errno($ch)) {
-				if ($csv) echo "Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch);
-			}
-			else {
-				$tripplan = json_decode($page); 
-				$plans = Array();
-				if (is_array($tripplan->plan->itineraries->itinerary)) {
-					foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {
-						$plans[floor($itinerary->duration / 60000) ] = $itinerary;
-					}
-				}
-				else {
-					$plans[floor($tripplan->plan->itineraries->itinerary->duration / 60000) ] = $tripplan->plan->itineraries->itinerary;
-				}
-				if ($csv) echo "$i,$j," . min(array_keys($plans)) . ",$latdeltasize, $londeltasize,$key\n";
-				if ($kml) {
-					$time = min(array_keys($plans));
-					$plan = "";
-					if (is_array($plans[min(array_keys($plans)) ]->legs->leg)) {
-						foreach ($plans[min(array_keys($plans)) ]->legs->leg as $legNumber => $leg) {
-							$plan .= processLeg($legNumber, $leg).",";
-						}
-					}
-					else {
-						$plan .= processLeg(0, $plans[min(array_keys($plans)) ]->legs->leg);
-					}
-						if (isset($tripplan->error) && $tripplan->error->id == 404) {
-							$time = 999;
-							$plan = "Trip not possible without excessive walking from nearest bus stop";
-						}
-					$testRegions[] = Array(
-						"lat" => $i,
-						"lon" => $j,
-						"time" => $time,
-						"latdeltasize" => $latdeltasize,
-						"londeltasize" => $londeltasize,
-						"regionname" => $key,
-						"plan" => $plan . "<br/><a href='". htmlspecialchars($url)."'>original plan</a>"
-					);
-					$regionTimes[] = $time;
-				}
-			}
-			flush();
-			ob_flush();
-			curl_close($ch);
-		}
-	}
-}
+			);
+			$request->metadata = Array( "i" => $i, "j" => $j, "key" => $key, "url" => $url);
+			$rc->add($request);
+		}
+	}
+}
+$rc->execute();
 if ($kml) {
 	$colorSteps = 9;
 	//$minTime = min($regionTimes);
 	//$maxTime = max($regionTimes);
 	//$rangeTime = $maxTime - $minTime;
 	//$deltaTime = $rangeTime / $colorSteps;
-	$Gradients = Gradient(strrev("66FF00"), strrev("FF0000"), $colorSteps); // KML is BGR not RGB so strrev
+	$Gradients = Gradient(strrev("66FF00") , strrev("FF0000") , $colorSteps); // KML is BGR not RGB so strrev
 	foreach ($testRegions as $testRegion) {
 		//$band = (floor(($testRegion[time] - $minTime) / $deltaTime));
 		$band = (floor($testRegion[time] / 10));
 		if ($band > $colorSteps) $band = $colorSteps;
 		echo "<Placemark>
   <name>" . $testRegion['regionname'] . " time {$testRegion['time']} band $band</name>
-  <description> {$testRegion['plan']} </description>
+  <description> <![CDATA[ {$testRegion['plan']}  ]]> </description>
     <Style>
         <PolyStyle>
             <color>c7" . $Gradients[$band] . "</color>" . // 7f = 50% alpha, c7=78%

--- a/labs/tripPlannerTester.php
+++ b/labs/tripPlannerTester.php
@@ -3,7 +3,9 @@
     <script src="openlayers/OpenLayers.js"></script>
  <SCRIPT TYPE="text/javascript" SRC="OpenStreetMap.js"></SCRIPT> 
     <script type="text/javascript">
-
+        var map,select;
+       
+	
 function init()
 {
     var extent = new OpenLayers.Bounds(148.98, -35.48, 149.25, -35.15);
@@ -16,13 +18,13 @@
 		}; 
  
 		// create the ol map object
-		var map = new OpenLayers.Map('map', options);
+		map = new OpenLayers.Map('map', options);
     
 var osmtiles = new OpenLayers.Layer.OSM("OSM");
 
 var nearmap = new OpenLayers.Layer.OSM.NearMap("NearMap");
 
-    var tripplantest = new OpenLayers.Layer.GML("tripplantest", "tripPlannerTester.kml.php", {
+    var tripplantest = new OpenLayers.Layer.GML("tripplantest", "tripPlannerTester.kml", {
         format: OpenLayers.Format.KML,
         formatOptions: {
             extractStyles: true,
@@ -44,9 +46,45 @@
     {
         displayProjection: new OpenLayers.Projection("EPSG:900913")
     }));
+    
+  select = new OpenLayers.Control.SelectFeature(tripplantest);
+            
+            tripplantest.events.on({
+                "featureselected": onFeatureSelect,
+                "featureunselected": onFeatureUnselect
+            });
+ 
+            map.addControl(select);
+            select.activate();   
 
 }
- 
+ function onPopupClose(evt) {
+            select.unselectAll();
+        }
+        function onFeatureSelect(event) {
+            var feature = event.feature;
+            // Since KML is user-generated, do naive protection against
+            // Javascript.
+            var content = "<h2>"+feature.attributes.name + "</h2>" + feature.attributes.description;
+            if (content.search("<script") != -1) {
+                content = "Content contained Javascript! Escaped content below.<br />" + content.replace(/</g, "&lt;");
+            }
+            popup = new OpenLayers.Popup.FramedCloud("chicken", 
+                                     feature.geometry.getBounds().getCenterLonLat(),
+                                     new OpenLayers.Size(100,100),
+                                     content,
+                                     null, true, onPopupClose);
+            feature.popup = popup;
+            map.addPopup(popup);
+        }
+        function onFeatureUnselect(event) {
+            var feature = event.feature;
+            if(feature.popup) {
+                map.removePopup(feature.popup);
+                feature.popup.destroy();
+                delete feature.popup;
+            }
+        }
     </script>
 
   </head>

--- a/layar_api.php
+++ b/layar_api.php
@@ -5,36 +5,31 @@
 $output['layer'] = "canberrabusstops";
 $max_page = 10;
 $max_results = 50;
-$page_start = 0 + filter_var($_REQUEST['pageKey'], FILTER_SANITIZE_NUMBER_INT);
-$page_end = $max_page + filter_var($_REQUEST['pageKey'], FILTER_SANITIZE_NUMBER_INT);
-$lat = filter_var($_REQUEST['lat'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
-$lon = filter_var($_REQUEST['lon'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
-$url = $APIurl . "/json/neareststops?lat=$lat&lon=$lon&limit=50";
-$contents = json_decode(getPage($url));
-debug(print_r($contents, true));
+$page_start = 0 + $pageKey;
+$page_end = $max_page + $pageKey;
+$contents = getNearbyStops($lat, $lon, 50, $max_distance);
 $stopNum = 0;
-foreach ($contents as $row) {
+foreach ($contents as $stop) {
 	$stopNum++;
 	if ($stopNum > $page_start && $stopNum <= $page_end) {
 		$hotspot = Array();
-		$hotspot['id'] = $row[0];
-		$hotspot['title'] = $row[1];
+		$hotspot['id'] = $stop['stop_id'];
+		$hotspot['title'] = $stop['stop_name'];
 		$hotspot['type'] = 0;
-		$hotspot['lat'] = floor($row[2] * 1000000);
-		$hotspot['lon'] = floor($row[3] * 1000000);
-		$hotspot['distance'] = distance($row[2], $row[3], $_REQUEST['lat'], $_REQUEST['lon']);
+		$hotspot['lat'] = floor($stop['stop_lat'] * 1000000);
+		$hotspot['lon'] = floor($stop['stop_lon'] * 1000000);
+		$hotspot['distance'] = floor($stop['distance']);
+		$hotspot['attribution'] = "ACTION Buses";
 		$hotspot['actions'] = Array(
 			Array(
 				"label" => 'View more trips/information',
-				'uri' => 'http://bus.lambdacomplex.org/' . 'stop.php?stopid=' . $row[0]
+				'uri' => 'http://bus.lambdacomplex.org/' . 'stop.php?stopid=' . $stop['stop_id']
 			)
 		);
-		$url = $APIurl . "/json/stoptrips?stop=" . $row[0] . "&time=" . midnight_seconds() . "&service_period=" . service_period() . "&limit=4&time_range=" . strval(90 * 60);
-		$trips = json_decode(getPage($url));
-		debug(print_r($trips, true));
+		$trips = getStopTripsWithTimes($stop['stop_id'], "", "", "", 3);
 		foreach ($trips as $key => $row) {
 			if ($key < 3) {
-				$hotspot['line' . strval($key + 2) ] = $row[1][1] . ' @ ' . midnight_seconds_to_time($row[0]);
+				$hotspot['line' . strval($key + 2) ] = $row['route_short_name'] . ' ' . $row['route_long_name'] . ' @ ' . $row['arrival_time'];
 			}
 		}
 		if (sizeof($trips) == 0) $hotspot['line2'] = 'No trips in the near future.';
@@ -49,7 +44,7 @@
 	$output['errorString'] = 'no results, try increasing range';
 	$output['errorCode'] = 21;
 }
-if ($page_end >= $max_results || sizeof($hotspot) < $max_page) {
+if ($page_end >= $max_results || sizeof($contents) < $page_start+$max_page) {
 	$output["morePages"] = false;
 	$output["nextPageKey"] = null;
 }

--- /dev/null
+++ b/lib/GoogleMapUtility.php
@@ -1,1 +1,97 @@
+<?php
+/*
+*DISCLAIMER
+* 
+*THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES *OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, *INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*	@author: Olivier G. <olbibigo_AT_gmail_DOT_com>
+*	@version: 1.1
+*	@history:
+*		1.0	creation
+		1.1	disclaimer added
+*/
+class GoogleMapUtility {
+	const TILE_SIZE = 256;
+	
+	//(lat, lng, z) -> parent tile (X,Y)
+	public static function getTileXY($lat, $lng, $zoom) {
+		$normalised = GoogleMapUtility::_toNormalisedMercatorCoords(GoogleMapUtility::_toMercatorCoords($lat, $lng));
+		$scale = 1 << ($zoom);
+		return new Point(
+			(int)($normalised->x * $scale), 
+			(int)($normalised->y * $scale)
+		);
+	}//toTileXY
+	
+	//(lat, lng, z) -> (x,y) with (0,0) in the upper left corner of the MAP
+	public static function getPixelCoords($lat, $lng, $zoom) {
+		$normalised = GoogleMapUtility::_toNormalisedMercatorCoords(GoogleMapUtility::_toMercatorCoords($lat, $lng));
+		$scale = (1 << ($zoom)) * GoogleMapUtility::TILE_SIZE;
+		return new Point(
+			(int)($normalised->x * $scale), 
+			(int)($normalised->y * $scale)
+		);
+	}//getPixelCoords
 
+	//(lat, lng, z) -> (x,y) in the upper left corner of the TILE ($X, $Y)
+	public static function getOffsetPixelCoords($lat,$lng,$zoom, $X, $Y) {
+		$pixelCoords = GoogleMapUtility::getPixelCoords($lat, $lng, $zoom);
+		return new Point(
+			$pixelCoords->x - $X * GoogleMapUtility::TILE_SIZE, 
+			$pixelCoords->y - $Y * GoogleMapUtility::TILE_SIZE
+		);
+	}//getPixelOffsetInTile
+	
+	public static function getTileRect($X,$Y,$zoom) {
+		$tilesAtThisZoom = 1 << $zoom;
+		$lngWidth = 360.0 / $tilesAtThisZoom;
+		$lng = -180 + ($X * $lngWidth);	
+		$latHeightMerc = 1.0 / $tilesAtThisZoom;
+		$topLatMerc = $Y * $latHeightMerc;
+		$bottomLatMerc = $topLatMerc + $latHeightMerc;
+		$bottomLat = (180 / M_PI) * ((2 * atan(exp(M_PI * (1 - (2 * $bottomLatMerc))))) - (M_PI / 2));
+		$topLat = (180 / M_PI) * ((2 * atan(exp(M_PI * (1 - (2 * $topLatMerc))))) - (M_PI / 2));
+		$latHeight = $topLat - $bottomLat;
+		return new Boundary($lng, $bottomLat, $lngWidth, $latHeight);
+	}//getTileRect	
+
+	private static function _toMercatorCoords($lat, $lng) {
+		if ($lng > 180) {
+			$lng -= 360;
+		}
+		$lng /= 360;
+		$lat = asinh(tan(deg2rad($lat)))/M_PI/2;
+		return new Point($lng, $lat);
+	}//_toMercatorCoords
+
+	private static function _toNormalisedMercatorCoords($point) {
+		$point->x += 0.5;
+		$point->y = abs($point->y-0.5);
+		return $point;
+	}//_toNormalisedMercatorCoords
+}//GoogleMapUtility
+
+class Point {
+	public $x,$y;
+	function __construct($x,$y) {
+		$this->x = $x;
+		$this->y = $y;
+	}
+	function __toString() {
+		return "({$this->x},{$this->y})";
+	}
+}//Point
+
+class Boundary {
+	public $x,$y,$width,$height;
+	function __construct($x,$y,$width,$height) {
+		$this->x = $x;
+		$this->y = $y;
+		$this->width = $width;
+		$this->height = $height;
+	}
+	function __toString() {
+		return "({$this->x} x {$this->y},{$this->width},{$this->height})";
+	}
+}//Boundary
+?>

file:b/lib/HeatMap.php (new)
--- /dev/null
+++ b/lib/HeatMap.php
@@ -1,1 +1,275 @@
-
+<?php
+/*
+*DISCLAIMER
+* http://blog.gmapify.fr/create-beautiful-tiled-heat-maps-with-php-and-gd
+*THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES *OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, *INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*	@author: Olivier G. <olbibigo_AT_gmail_DOT_com>
+*	@version: 1.0
+*	@history:
+*		1.0	creation
+*/
+	define('PI2', 2*M_PI);
+
+	class HeatMapPoint{
+		public $x,$y;
+		function __construct($x,$y) {
+			$this->x = $x;
+			$this->y = $y;
+		}
+		function __toString() {
+			return "({$this->x},{$this->y})";
+		}
+	}//Point
+
+	class HeatMap{
+		//TRANSPARENCY
+		public static $WITH_ALPHA = 0;
+		public static $WITH_TRANSPARENCY = 1;
+		//GRADIENT STYLE
+		public static $GRADIENT_CLASSIC = 'classic';
+		public static $GRADIENT_FIRE = 'fire';
+		public static $GRADIENT_PGAITCH = 'pgaitch';
+		//GRADIENT MODE (for heatImage)
+		public static $GRADIENT_NO_NEGATE_NO_INTERPOLATE = 0;
+		public static $GRADIENT_NO_NEGATE_INTERPOLATE = 1;
+		public static $GRADIENT_NEGATE_NO_INTERPOLATE = 2;
+		public static $GRADIENT_NEGATE_INTERPOLATE = 3;
+		//NOT PROCESSED PIXEL (for heatImage)
+		public static $KEEP_VALUE = 0;
+		public static $NO_KEEP_VALUE = 1;
+		//CONSTRAINTS
+		private static $MIN_RADIUS = 2;//in px
+		private static $MAX_RADIUS = 50;//in px
+		private static $MAX_IMAGE_SIZE = 10000;//in px
+		
+		//generate an $image_width by $image_height pixels heatmap image of $points
+		public static function createImage($data, $image_width, $image_height, $mode=0, $spot_radius = 30, $dimming = 75, $gradient_name = 'classic'){
+			$_gradient_name = $gradient_name;
+			if(($_gradient_name != self::$GRADIENT_CLASSIC) && ($_gradient_name != self::$GRADIENT_FIRE) && ($_gradient_name != self::$GRADIENT_PGAITCH)){
+				$_gradient_name = self::$GRADIENT_CLASSIC;
+			}
+			$_image_width = min(self::$MAX_IMAGE_SIZE, max(0, intval($image_width)));
+			$_image_height = min(self::$MAX_IMAGE_SIZE, max(0, intval($image_height)));
+			$_spot_radius = min(self::$MAX_RADIUS, max(self::$MIN_RADIUS, intval($spot_radius)));
+			$_dimming = min(255, max(0, intval($dimming)));
+			if(!is_array($data)){
+				return false;
+			}
+			$im = imagecreatetruecolor($_image_width, $_image_height);
+			$white = imagecolorallocate($im, 255, 255, 255);
+			imagefill($im, 0, 0, $white);
+			if(self::$WITH_ALPHA == $mode){
+				imagealphablending($im, false);
+				imagesavealpha($im,true);
+			}
+			//Step 1: create grayscale image
+			foreach($data as $datum){
+				if( (is_array($datum) && (count($datum)==1)) || (!is_array($datum) && ('HeatMapPoint' == get_class($datum)))){//Plot points
+					if('HeatMapPoint' != get_class($datum)){
+						$datum = $datum[0];
+					}
+					self::_drawCircularGradient($im, $datum->x, $datum->y, $_spot_radius, $_dimming);
+				}else if(is_array($datum)){//Draw lines
+					$length = count($datum)-1;
+					for($i=0; $i < $length; ++$i){//Loop through points
+						//Bresenham's algorithm to plot from from $datum[$i] to $datum[$i+1];
+						self::_drawBilinearGradient($im, $datum[$i], $datum[$i+1], $_spot_radius, $_dimming);
+					}
+				}
+			}
+			//Gaussian filter
+			if($_spot_radius >= 30){
+				imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR);
+			}
+			//Step 2: create colored image
+			if(FALSE === ($grad_rgba = self::_createGradient($im, $mode, $_gradient_name))){
+				return FALSE;
+			}
+			$grad_size = count($grad_rgba);
+			for($x=0; $x <$_image_width; ++$x){
+				for($y=0; $y <$_image_height; ++$y){
+					$level = imagecolorat($im, $x, $y) & 0xFF;
+					if( ($level >= 0) && ($level < $grad_size) ){
+						imagesetpixel($im, $x, $y, $grad_rgba[imagecolorat($im, $x, $y) & 0xFF]);
+					}
+				}
+			}
+			if(self::$WITH_TRANSPARENCY == $mode){
+				imagecolortransparent($im, $grad_rgba[count($grad_rgba)-1]);
+			}
+			return $im;
+		}//createImage
+
+		//Heat an image
+		public static function heatImage($filepath, $gradient_name = 'classic', $mode= 0, $min_level=0, $max_level=255, $gradient_interpolate=0, $keep_value=0){
+			$_gradient_name = $gradient_name;
+			if(($_gradient_name != self::$GRADIENT_CLASSIC) && ($_gradient_name != self::$GRADIENT_FIRE) && ($_gradient_name != self::$GRADIENT_PGAITCH)){
+				$_gradient_name = self::$GRADIENT_CLASSIC;
+			}
+			$_min_level = min(255, max(0, intval($min_level)));
+			$_max_level = min(255, max(0, intval($max_level)));
+
+			//try opening jpg first then png then gif format
+			if(FALSE === ($im = @imagecreatefromjpeg($filepath))){
+				if(FALSE === ($im = @imagecreatefrompng($filepath))){
+					if(FALSE === ($im = @imagecreatefromgif($filepath))){
+						return FALSE;
+					}
+				}
+			}
+			if(self::$WITH_ALPHA == $mode){
+				imagealphablending($im, false);
+				imagesavealpha($im,true);
+			}
+			$width = imagesx($im);
+			$height = imagesy($im);	
+			if(FALSE === ($grad_rgba = self::_createGradient($im, $mode, $_gradient_name))){
+				return FALSE;
+			}
+			//Convert to grayscale
+			$grad_size = count($grad_rgba);
+			$level_range = $_max_level - $_min_level;
+			for($x=0; $x <$width; ++$x){
+				for($y=0; $y <$height; ++$y){
+					$rgb = imagecolorat($im, $x, $y);
+					$r = ($rgb >> 16) & 0xFF;
+					$g = ($rgb >> 8) & 0xFF;
+					$b = $rgb & 0xFF;
+					$gray_level = Min(255, Max(0, floor(0.33 * $r + 0.5 * $g + 0.16 * $b)));//between 0 and 255				
+					if( ($gray_level >= $_min_level) && ($gray_level <= $_max_level) ){
+						switch($gradient_interpolate){
+							case self::$GRADIENT_NO_NEGATE_NO_INTERPOLATE:
+								//$_max_level takes related lowest gradient color
+								//$_min_level takes related highest gradient color
+								$value = 255 - $gray_level;
+								break;
+							case self::$GRADIENT_NEGATE_NO_INTERPOLATE:
+								//$_max_level takes related highest gradient color
+								//$_min_level takes related lowest gradient color
+								$value = $gray_level;
+								break;
+							case self::$GRADIENT_NO_NEGATE_INTERPOLATE:
+								//$_max_level takes lowest gradient color
+								//$_min_level takes highest gradient color
+								$value = 255- floor(($gray_level - $_min_level) * $grad_size / $level_range);
+								break;
+							case self::$GRADIENT_NEGATE_INTERPOLATE:
+								//$_max_level takes highest gradient color
+								//$_min_level takes lowest gradient color
+								$value = floor(($gray_level - $_min_level) * $grad_size / $level_range);
+								break;
+							default:
+						}
+						imagesetpixel($im, $x, $y, $grad_rgba[$value]);
+					}else{
+						if(self::$KEEP_VALUE == $keep_value){
+							//Do nothing
+						}else{//self::$NO_KEEP_VALUE
+							imagesetpixel($im, $x, $y, imagecolorallocatealpha($im,0,0,0,0));
+						}
+					}
+				}
+			}			
+			if(self::$WITH_TRANSPARENCY == $mode){
+				imagecolortransparent($im, $grad_rgba[count($grad_rgba)-1]);
+			}
+			return $im;
+		}//heatImage
+		
+		private static function _drawCircularGradient(&$im, $center_x, $center_y, $spot_radius, $dimming){
+			$dirty = array();
+			$ratio = (255 - $dimming) / $spot_radius;
+			for($r=$spot_radius; $r > 0; --$r){
+				$channel = $dimming + $r * $ratio;
+				$angle_step = 0.45/$r; //0.01;
+				//Process pixel by pixel to draw a radial grayscale radient
+				for($angle=0; $angle <= PI2; $angle += $angle_step){
+					$x = floor($center_x + $r*cos($angle));
+					$y = floor($center_y + $r*sin($angle));
+					if(!isset($dirty[$x][$y])){
+						$previous_channel = @imagecolorat($im, $x, $y) & 0xFF;//grayscale so same value
+						$new_channel = Max(0, Min(255,($previous_channel * $channel)/255));
+						imagesetpixel($im, $x, $y, imagecolorallocate($im, $new_channel, $new_channel, $new_channel));
+						$dirty[$x][$y] = 0;
+					}
+				}
+			}
+		}//_drawCircularGradient
+		
+		private static function _drawBilinearGradient(&$im, $point0, $point1, $spot_radius, $dimming){
+			if($point0->x < $point1->x){
+				$x0 = $point0->x;
+				$y0 = $point0->y;
+				$x1 = $point1->x;
+				$y1 = $point1->y;
+			}else{
+				$x0 = $point1->x;
+				$y0 = $point1->y;
+				$x1 = $point0->x;
+				$y1 = $point0->y;
+			}
+
+			if( ($x0==$x1) && ($y0==$y1)){//check if same coordinates
+				return false;
+			}
+			$steep = (abs($y1 - $y0) > abs($x1 - $x0))? true: false;
+			if($steep){
+				list($x0, $y0) = array($y0, $x0);//swap
+				list($x1, $y1) = array($y1, $x1);//swap
+			}
+			if($x0>$x1){
+				list($x0, $x1) = array($x1, $x0);//swap
+				list($y0, $y1) = array($y1, $y0);//swap
+			}
+			$deltax = $x1 - $x0;
+			$deltay = abs($y1 - $y0);
+			$error = $deltax / 2;
+			$y = $y0;
+			if( $y0 < $y1){
+				$ystep = 1; 
+			}else{
+				$ystep = -1;
+			}
+			$step = max(1, floor($spot_radius/ 3));
+			for($x=$x0; $x<=$x1; ++$x){//Loop through x value
+				if(0==(($x-$x0) % $step)){
+					if($steep){
+						self::_drawCircularGradient(&$im, $y, $x, $spot_radius, $dimming);
+					}else{ 
+						self::_drawCircularGradient(&$im, $x, $y, $spot_radius, $dimming);
+					}
+				}
+				$error -= $deltay;
+				if($error<0){
+						$y = $y + $ystep;
+						$error = $error + $deltax;
+				}
+			}		
+		}//_drawBilinearGradient
+		
+		private static function _createGradient($im, $mode, $gradient_name){
+			//create the gradient from an image
+			if(FALSE === ($grad_im = imagecreatefrompng('gradient/'.$gradient_name.'.png'))){
+				return FALSE;
+			}
+			$width_g = imagesx($grad_im);
+			$height_g = imagesy($grad_im);
+			//Get colors along the longest dimension
+			//Max density is for lower channel value
+			for($y=$height_g-1; $y >= 0 ; --$y){
+					$rgb = imagecolorat($grad_im, 1, $y);
+					//Linear function
+					$alpha = Min(127, Max(0, floor(127 - $y/2)));
+					if(self::$WITH_ALPHA == $mode){
+						$grad_rgba[] = imagecolorallocatealpha($im, ($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF, $alpha);
+					}else{
+						$grad_rgba[] = imagecolorallocate($im, ($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF);
+					}
+			}
+			imagedestroy($grad_im);
+			unset($grad_im);
+			return($grad_rgba);
+		}//_createGradient
+	}//Heatmap
+?>

file:b/lib/postgis.sh (new)
--- /dev/null
+++ b/lib/postgis.sh
@@ -1,1 +1,3 @@
+createlang -d dbname plpgsql
+psql -d transitdata -f postgis.sql
 

file:b/lib/postgis.sql (new)
--- /dev/null
+++ b/lib/postgis.sql
@@ -1,1 +1,7788 @@
-
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+--
+-- $Id: postgis.sql.in.c 5385 2010-03-09 00:22:41Z pramsey $
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- http://postgis.refractions.net
+-- Copyright 2001-2003 Refractions Research Inc.
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+--
+-- WARNING: Any change in this file must be evaluated for compatibility.
+--          Changes cleanly handled by postgis_upgrade.sql are fine,
+--	    other changes will require a bump in Major version.
+--	    Currently only function replaceble by CREATE OR REPLACE
+--	    are cleanly handled.
+--
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+-- INSTALL VERSION: 1.5.1
+
+SET client_min_messages TO warning;
+
+BEGIN;
+
+-------------------------------------------------------------------
+--  SPHEROID TYPE
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_spheroid_in(cstring)
+	RETURNS spheroid
+	AS '$libdir/postgis-1.5','ellipsoid_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_spheroid_out(spheroid)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','ellipsoid_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION spheroid_in(cstring)
+	RETURNS spheroid
+	AS '$libdir/postgis-1.5','ellipsoid_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION spheroid_out(spheroid)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','ellipsoid_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE spheroid (
+	alignment = double,
+	internallength = 65,
+	input = spheroid_in,
+	output = spheroid_out
+);
+
+-------------------------------------------------------------------
+--  GEOMETRY TYPE (lwgeom)
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_in(cstring)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_out(geometry)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','LWGEOM_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_analyze(internal)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_analyze'
+	LANGUAGE 'C' VOLATILE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_recv(internal)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_recv'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_send(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_send'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_in(cstring)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_out(geometry)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','LWGEOM_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_analyze(internal)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_analyze'
+	LANGUAGE 'C' VOLATILE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_recv(internal)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_recv'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_send(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_send'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE geometry (
+	internallength = variable,
+	input = geometry_in,
+	output = geometry_out,
+	send = geometry_send,
+	receive = geometry_recv,
+	delimiter = ':',
+	analyze = geometry_analyze,
+	storage = main
+);
+
+-------------------------------------------
+-- Affine transforms
+-------------------------------------------
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Affine(geometry,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_affine'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Affine(geometry,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_affine'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Affine(geometry,float8,float8,float8,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $2, $3, 0,  $4, $5, 0,  0, 0, 1,  $6, $7, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Affine(geometry,float8,float8,float8,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $2, $3, 0,  $4, $5, 0,  0, 0, 1,  $6, $7, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION RotateZ(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  cos($2), -sin($2), 0,  sin($2), cos($2), 0,  0, 0, 1,  0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_RotateZ(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  cos($2), -sin($2), 0,  sin($2), cos($2), 0,  0, 0, 1,  0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Rotate(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT rotateZ($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Rotate(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT rotateZ($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION RotateX(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1, 1, 0, 0, 0, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_RotateX(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1, 1, 0, 0, 0, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION RotateY(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  cos($2), 0, sin($2),  0, 1, 0,  -sin($2), 0, cos($2), 0,  0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_RotateY(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  cos($2), 0, sin($2),  0, 1, 0,  -sin($2), 0, cos($2), 0,  0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Translate(geometry,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1, 1, 0, 0, 0, 1, 0, 0, 0, 1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Translate(geometry,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1, 1, 0, 0, 0, 1, 0, 0, 0, 1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Translate(geometry,float8,float8)
+	RETURNS geometry
+	AS 'SELECT translate($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Translate(geometry,float8,float8)
+	RETURNS geometry
+	AS 'SELECT translate($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Scale(geometry,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $2, 0, 0,  0, $3, 0,  0, 0, $4,  0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Scale(geometry,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $2, 0, 0,  0, $3, 0,  0, 0, $4,  0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Scale(geometry,float8,float8)
+	RETURNS geometry
+	AS 'SELECT scale($1, $2, $3, 1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Scale(geometry,float8,float8)
+	RETURNS geometry
+	AS 'SELECT scale($1, $2, $3, 1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION transscale(geometry,float8,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $4, 0, 0,  0, $5, 0,
+		0, 0, 1,  $2 * $4, $3 * $5, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_transscale(geometry,float8,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $4, 0, 0,  0, $5, 0,
+		0, 0, 1,  $2 * $4, $3 * $5, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION shift_longitude(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_longitude_shift'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_shift_longitude(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_longitude_shift'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-------------------------------------------------------------------
+--  BOX3D TYPE
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d_in(cstring)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d_out(box3d)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5', 'BOX3D_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d_in(cstring)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d_out(box3d)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5', 'BOX3D_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE box3d (
+	alignment = double,
+	internallength = 48,
+	input = box3d_in,
+	output = box3d_out
+);
+
+-- Temporary box3d aggregate type to retain full double precision
+-- for ST_Extent(). Should be removed when we change the output
+-- type of ST_Extent() to return something other than BOX2DFLOAT4.
+CREATE OR REPLACE FUNCTION box3d_extent_in(cstring)
+	RETURNS box3d_extent
+	AS '$libdir/postgis-1.5', 'BOX3D_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d_extent_out(box3d_extent)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5', 'BOX3D_extent_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE box3d_extent (
+	alignment = double,
+	internallength = 48,
+	input = box3d_extent_in,
+	output = box3d_extent_out
+);
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION box3d_extent(box3d_extent)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_extent_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box2d(box3d_extent)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX3D_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(box3d_extent)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX3D_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- End of temporary hack
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION xmin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_xmin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_XMin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_xmin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ymin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_ymin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_YMin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_ymin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION zmin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_zmin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_ZMin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_zmin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION xmax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_xmax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_XMax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_xmax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ymax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_ymax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_YMax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_ymax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION zmax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_zmax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_ZMax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_zmax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-------------------------------------------------------------------
+--  CHIP TYPE
+-------------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION chip_in(cstring)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION chip_out(chip)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','CHIP_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION ST_chip_in(cstring)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION ST_chip_out(chip)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','CHIP_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE chip (
+	alignment = double,
+	internallength = variable,
+	input = chip_in,
+	output = chip_out,
+	storage = extended
+);
+
+-----------------------------------------------------------------------
+-- BOX2D
+-----------------------------------------------------------------------
+
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d_in(cstring)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d_out(box2d)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+CREATE OR REPLACE FUNCTION box2d_in(cstring)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box2d_out(box2d)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE box2d (
+	internallength = 16,
+	input = box2d_in,
+	output = box2d_out,
+	storage = plain
+);
+
+
+-------------------------------------------------------------------
+-- BTREE indexes
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_lt(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_lt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_le(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_le'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_gt(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_gt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_ge(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_ge'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_eq(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_eq'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_cmp(geometry, geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5', 'lwgeom_cmp'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_lt(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_lt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_le(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_le'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_gt(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_gt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_ge(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_ge'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_eq(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_eq'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_cmp(geometry, geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5', 'lwgeom_cmp'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+--
+-- Sorting operators for Btree
+--
+
+CREATE OPERATOR < (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_lt,
+	COMMUTATOR = '>', NEGATOR = '>=',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR <= (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_le,
+	COMMUTATOR = '>=', NEGATOR = '>',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR = (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_eq,
+	COMMUTATOR = '=', -- we might implement a faster negator here
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR >= (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_ge,
+	COMMUTATOR = '<=', NEGATOR = '<',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+CREATE OPERATOR > (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_gt,
+	COMMUTATOR = '<', NEGATOR = '<=',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+
+CREATE OPERATOR CLASS btree_geometry_ops
+	DEFAULT FOR TYPE geometry USING btree AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	geometry_cmp (geometry, geometry);
+
+
+
+-------------------------------------------------------------------
+-- GiST indexes
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION postgis_gist_sel (internal, oid, internal, int4)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_sel'
+	LANGUAGE 'C';
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION postgis_gist_joinsel(internal, oid, internal, smallint)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_joinsel'
+	LANGUAGE 'C';
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_postgis_gist_sel (internal, oid, internal, int4)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_sel'
+	LANGUAGE 'C';
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_postgis_gist_joinsel(internal, oid, internal, smallint)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_joinsel'
+	LANGUAGE 'C';
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overleft(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overleft'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overright(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overright'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overabove(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overabove'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overbelow(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overbelow'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_left(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_left'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_right(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_right'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_above(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_above'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_below(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_below'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_contain(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_contain'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_contained(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_contained'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overlap(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overlap'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_same(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_samebox'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION geometry_same(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_samebox'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_gist_sel (internal, oid, internal, int4)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_sel'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION geometry_gist_joinsel(internal, oid, internal, smallint)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_joinsel'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION geometry_overleft(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overleft'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_overright(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overright'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_overabove(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overabove'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_overbelow(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overbelow'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_left(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_left'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_right(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_right'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_above(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_above'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_below(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_below'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_contain(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_contain'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_contained(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_contained'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_overlap(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overlap'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_samebox(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_samebox'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OPERATOR << (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_left,
+	COMMUTATOR = '>>',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR &< (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overleft,
+	COMMUTATOR = '&>',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR <<| (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_below,
+	COMMUTATOR = '|>>',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR &<| (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overbelow,
+	COMMUTATOR = '|&>',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR && (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overlap,
+	COMMUTATOR = '&&',
+	RESTRICT = geometry_gist_sel, JOIN = geometry_gist_joinsel
+);
+
+CREATE OPERATOR &> (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overright,
+	COMMUTATOR = '&<',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR >> (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_right,
+	COMMUTATOR = '<<',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR |&> (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overabove,
+	COMMUTATOR = '&<|',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR |>> (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_above,
+	COMMUTATOR = '<<|',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR ~= (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_samebox,
+	COMMUTATOR = '~=',
+	RESTRICT = eqsel, JOIN = eqjoinsel
+);
+
+CREATE OPERATOR @ (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_contained,
+	COMMUTATOR = '~',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR ~ (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_contain,
+	COMMUTATOR = '@',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+-- gist support functions
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_consistent(internal,geometry,int4)
+	RETURNS bool
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_consistent'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_compress(internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5','LWGEOM_gist_compress'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_penalty(internal,internal,internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_penalty'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_picksplit(internal, internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_picksplit'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_union(bytea, internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_union'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_same(box2d, box2d, internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_same'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_decompress(internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_decompress'
+	LANGUAGE 'C';
+
+-------------------------------------------
+-- GIST opclass index binding entries.
+-------------------------------------------
+--
+-- Create opclass index bindings for PG>=73
+--
+
+CREATE OPERATOR CLASS gist_geometry_ops
+	DEFAULT FOR TYPE geometry USING gist AS
+	STORAGE 	box2d,
+	OPERATOR        1        << 	,
+	OPERATOR        2        &<	,
+	OPERATOR        3        &&	,
+	OPERATOR        4        &>	,
+	OPERATOR        5        >>	,
+	OPERATOR        6        ~=	,
+	OPERATOR        7        ~	,
+	OPERATOR        8        @	,
+	OPERATOR	9	 &<|	,
+	OPERATOR	10	 <<|	,
+	OPERATOR	11	 |>>	,
+	OPERATOR	12	 |&>	,
+	FUNCTION        1        LWGEOM_gist_consistent (internal, geometry, int4),
+	FUNCTION        2        LWGEOM_gist_union (bytea, internal),
+	FUNCTION        3        LWGEOM_gist_compress (internal),
+	FUNCTION        4        LWGEOM_gist_decompress (internal),
+	FUNCTION        5        LWGEOM_gist_penalty (internal, internal, internal),
+	FUNCTION        6        LWGEOM_gist_picksplit (internal, internal),
+	FUNCTION        7        LWGEOM_gist_same (box2d, box2d, internal);
+
+-------------------------------------------
+-- other lwgeom functions
+-------------------------------------------
+
+CREATE OR REPLACE FUNCTION addbbox(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_addBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_addbbox(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_addBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION dropbbox(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_dropBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_dropbbox(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_dropBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION getsrid(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','LWGEOM_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION getbbox(geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_getbbox(geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION hasbbox(geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_hasBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_hasbbox(geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_hasBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-------------------------------------------
+--- CHIP functions
+-------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION srid(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_srid(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION height(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getHeight'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_height(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getHeight'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION factor(chip)
+	RETURNS FLOAT4
+	AS '$libdir/postgis-1.5','CHIP_getFactor'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_factor(chip)
+	RETURNS FLOAT4
+	AS '$libdir/postgis-1.5','CHIP_getFactor'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION width(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getWidth'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_width(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getWidth'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION datatype(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getDatatype'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_datatype(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getDatatype'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION compression(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getCompression'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_compression(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getCompression'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION setSRID(chip,int4)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_setSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION setFactor(chip,float4)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_setFactor'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_setFactor(chip,float4)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_setFactor'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- DEBUG
+------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION mem_size(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_mem_size'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_mem_size(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_mem_size'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION summary(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'LWGEOM_summary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_summary(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'LWGEOM_summary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION npoints(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_npoints'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_npoints(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_npoints'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION nrings(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_nrings'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_nrings(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_nrings'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- Misures
+------------------------------------------------------------------------
+
+-- this is a fake (for back-compatibility)
+-- uses 3d if 3d is available, 2d otherwise
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length3d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length3d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION length(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: length2d(geometry)
+CREATE OR REPLACE FUNCTION ST_Length(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- this is a fake (for back-compatibility)
+-- uses 3d if 3d is available, 2d otherwise
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length3d_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length_ellipsoid_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length3d_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length_ellipsoid_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length_ellipsoid_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length_ellipsoid_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length2d_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length2d_ellipsoid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length2d_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length2d_ellipsoid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- this is a fake (for back-compatibility)
+-- uses 3d if 3d is available, 2d otherwise
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION perimeter3d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_perimeter3d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION perimeter2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter2d_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_perimeter2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter2d_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION perimeter(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: perimeter2d(geometry)
+CREATE OR REPLACE FUNCTION ST_Perimeter(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter2d_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- this is an alias for 'area(geometry)'
+-- there is nothing such an 'area3d'...
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION area2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_area_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+-- Deprecation in 1.3.4
+CREATE OR REPLACE FUNCTION ST_area2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_area_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION area(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_area_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: area(geometry)
+CREATE OR REPLACE FUNCTION ST_Area(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_area_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION distance_spheroid(geometry,geometry,spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_distance_ellipsoid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_distance_spheroid(geometry,geometry,spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_distance_ellipsoid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION distance_sphere(geometry,geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_distance_sphere'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_distance_sphere(geometry,geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_distance_sphere'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Minimum distance. 2d only.
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION distance(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_mindistance2d'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- PostGIS equivalent function: distance(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Distance(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_mindistance2d'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION point_inside_circle(geometry,float8,float8,float8)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_inside_circle_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_point_inside_circle(geometry,float8,float8,float8)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_inside_circle_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION azimuth(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_azimuth'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_azimuth(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_azimuth'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- MISC
+------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_2d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_2d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_3dz(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_3dz(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- an alias for force_3dz
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_3d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_3d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_3dm(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_3dm(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_4d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_4d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_4d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_4d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_collection(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_collection(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_CollectionExtract(geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'ST_CollectionExtract'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION multi(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_multi'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_multi(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_multi'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION expand(box3d,float8)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Expand(box3d,float8)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION expand(box2d,float8)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_expand(box2d,float8)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION expand(geometry,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_expand(geometry,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION envelope(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_envelope'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: envelope(geometry)
+CREATE OR REPLACE FUNCTION ST_Envelope(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_envelope'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION reverse(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_reverse'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Reverse(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_reverse'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ForceRHR(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_forceRHR_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_ForceRHR(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_forceRHR_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION noop(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_noop'
+	LANGUAGE 'C' VOLATILE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_noop(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_noop'
+	LANGUAGE 'C' VOLATILE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION zmflag(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_zmflag'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION ST_zmflag(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_zmflag'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ndims(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_ndims'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_NDims(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_ndims'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsEWKT(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asEWKT'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsEWKT(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asEWKT'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsEWKB(geometry)
+	RETURNS BYTEA
+	AS '$libdir/postgis-1.5','WKBFromLWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsEWKB(geometry)
+	RETURNS BYTEA
+	AS '$libdir/postgis-1.5','WKBFromLWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsHEXEWKB(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asHEXEWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsHEXEWKB(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asHEXEWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsHEXEWKB(geometry, text)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asHEXEWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsHEXEWKB(geometry, text)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asHEXEWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsEWKB(geometry,text)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','WKBFromLWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsEWKB(geometry,text)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','WKBFromLWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromEWKB(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOMFromWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomFromEWKB(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOMFromWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromEWKT(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','parse_WKT_lwgeom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomFromEWKT(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','parse_WKT_lwgeom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_cache_bbox()
+	RETURNS trigger
+	AS '$libdir/postgis-1.5', 'cache_bbox'
+	LANGUAGE 'C';
+
+------------------------------------------------------------------------
+-- CONSTRUCTORS
+------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePoint(float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePoint(float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePoint(float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePoint(float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePoint(float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePoint(float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePointM(float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint3dm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.3.4
+CREATE OR REPLACE FUNCTION ST_MakePointM(float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint3dm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakeBox2d(geometry, geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_construct'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakeBox2d(geometry, geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_construct'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakeBox3d(geometry, geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_construct'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakeBox3d(geometry, geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_construct'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION makeline_garray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakeLine_GArray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_MakeLine (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromMultiPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_from_mpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LineFromMultiPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_from_mpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakeLine(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakeLine(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AddPoint(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_addpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AddPoint(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_addpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AddPoint(geometry, geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_addpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AddPoint(geometry, geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_addpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION RemovePoint(geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_removepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_RemovePoint(geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_removepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SetPoint(geometry, integer, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_setpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SetPoint(geometry, integer, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_setpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_MakeEnvelope(float8, float8, float8, float8, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'ST_MakeEnvelope'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePolygon(geometry, geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePolygon(geometry, geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePolygon(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePolygon(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION BuildArea(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_buildarea'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_BuildArea(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_buildarea'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Polygonize_GArray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'polygonize_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION ST_Polygonize_GArray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'polygonize_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_Polygonize (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'polygonize_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineMerge(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'linemerge'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LineMerge(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'linemerge'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+
+CREATE TYPE geometry_dump AS (path integer[], geom geometry);
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Dump(geometry)
+	RETURNS SETOF geometry_dump
+	AS '$libdir/postgis-1.5', 'LWGEOM_dump'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Dump(geometry)
+	RETURNS SETOF geometry_dump
+	AS '$libdir/postgis-1.5', 'LWGEOM_dump'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION DumpRings(geometry)
+	RETURNS SETOF geometry_dump
+	AS '$libdir/postgis-1.5', 'LWGEOM_dump_rings'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_DumpRings(geometry)
+	RETURNS SETOF geometry_dump
+	AS '$libdir/postgis-1.5', 'LWGEOM_dump_rings'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- _ST_DumpPoints()
+-----------------------------------------------------------------------
+-- A helper function for ST_DumpPoints(geom)
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_DumpPoints(the_geom geometry, cur_path integer[]) RETURNS SETOF geometry_dump AS $$
+DECLARE
+  tmp geometry_dump;
+  tmp2 geometry_dump;
+  nb_points integer;
+  nb_geom integer;
+  i integer;
+  j integer;
+  g geometry;
+  
+BEGIN
+  
+  RAISE DEBUG '%,%', cur_path, ST_GeometryType(the_geom);
+
+  -- Special case (MULTI* OR GEOMETRYCOLLECTION) : iterate and return the DumpPoints of the geometries
+  SELECT ST_NumGeometries(the_geom) INTO nb_geom;
+
+  IF (nb_geom IS NOT NULL) THEN
+    
+    i = 1;
+    FOR tmp2 IN SELECT (ST_Dump(the_geom)).* LOOP
+
+      FOR tmp IN SELECT * FROM _ST_DumpPoints(tmp2.geom, cur_path || tmp2.path) LOOP
+	    RETURN NEXT tmp;
+      END LOOP;
+      i = i + 1;
+      
+    END LOOP;
+
+    RETURN;
+  END IF;
+  
+
+  -- Special case (POLYGON) : return the points of the rings of a polygon
+  IF (ST_GeometryType(the_geom) = 'ST_Polygon') THEN
+
+    FOR tmp IN SELECT * FROM _ST_DumpPoints(ST_ExteriorRing(the_geom), cur_path || ARRAY[1]) LOOP
+      RETURN NEXT tmp;
+    END LOOP;
+    
+    j := ST_NumInteriorRings(the_geom);
+    FOR i IN 1..j LOOP
+        FOR tmp IN SELECT * FROM _ST_DumpPoints(ST_InteriorRingN(the_geom, i), cur_path || ARRAY[i+1]) LOOP
+          RETURN NEXT tmp;
+        END LOOP;
+    END LOOP;
+    
+    RETURN;
+  END IF;
+
+    
+  -- Special case (POINT) : return the point
+  IF (ST_GeometryType(the_geom) = 'ST_Point') THEN
+
+    tmp.path = cur_path || ARRAY[1];
+    tmp.geom = the_geom;
+
+    RETURN NEXT tmp;
+    RETURN;
+
+  END IF;
+
+
+  -- Use ST_NumPoints rather than ST_NPoints to have a NULL value if the_geom isn't
+  -- a LINESTRING or CIRCULARSTRING.
+  SELECT ST_NumPoints(the_geom) INTO nb_points;
+
+  -- This should never happen
+  IF (nb_points IS NULL) THEN
+    RAISE EXCEPTION 'Unexpected error while dumping geometry %', ST_AsText(the_geom);
+  END IF;
+
+  FOR i IN 1..nb_points LOOP
+    tmp.path = cur_path || ARRAY[i];
+    tmp.geom := ST_PointN(the_geom, i);
+    RETURN NEXT tmp;
+  END LOOP;
+   
+END
+$$ LANGUAGE plpgsql;
+
+-----------------------------------------------------------------------
+-- ST_DumpPoints()
+-----------------------------------------------------------------------
+-- This function mimicks that of ST_Dump for collections, but this function 
+-- that returns a path and all the points that make up a particular geometry.
+-- This current implementation in plpgsql does not scale very well at all.
+-- and should be ported to C at some point.
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_DumpPoints(geometry) RETURNS SETOF geometry_dump AS $$
+  SELECT * FROM _ST_DumpPoints($1, NULL);
+$$ LANGUAGE SQL;
+
+
+------------------------------------------------------------------------
+
+--
+-- Aggregate functions
+--
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION combine_bbox(box2d,geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Combine_BBox(box2d,geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Temporary hack function
+CREATE OR REPLACE FUNCTION combine_bbox(box3d_extent,geometry)
+	RETURNS box3d_extent
+	AS '$libdir/postgis-1.5', 'BOX3D_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Temporary hack function
+CREATE OR REPLACE FUNCTION ST_Combine_BBox(box3d_extent,geometry)
+	RETURNS box3d_extent
+	AS '$libdir/postgis-1.5', 'BOX3D_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE Extent(
+	sfunc = ST_combine_bbox,
+	basetype = geometry,
+	stype = box3d_extent
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Extent(
+	sfunc = ST_combine_bbox,
+	basetype = geometry,
+	stype = box3d_extent
+	);
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION combine_bbox(box3d,geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Combine_BBox(box3d,geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE Extent3d(
+	sfunc = combine_bbox,
+	basetype = geometry,
+	stype = box3d
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Extent3d(
+	sfunc = ST_combine_bbox,
+	basetype = geometry,
+	stype = box3d
+	);
+
+-----------------------------------------------------------------------
+-- ESTIMATED_EXTENT( <schema name>, <table name>, <column name> )
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION estimated_extent(text,text,text) RETURNS box2d AS
+	'$libdir/postgis-1.5', 'LWGEOM_estimated_extent'
+	LANGUAGE 'C' IMMUTABLE STRICT SECURITY DEFINER;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_estimated_extent(text,text,text) RETURNS box2d AS
+	'$libdir/postgis-1.5', 'LWGEOM_estimated_extent'
+	LANGUAGE 'C' IMMUTABLE STRICT SECURITY DEFINER;
+
+-----------------------------------------------------------------------
+-- ESTIMATED_EXTENT( <table name>, <column name> )
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION estimated_extent(text,text) RETURNS box2d AS
+	'$libdir/postgis-1.5', 'LWGEOM_estimated_extent'
+	LANGUAGE 'C' IMMUTABLE STRICT SECURITY DEFINER;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_estimated_extent(text,text) RETURNS box2d AS
+	'$libdir/postgis-1.5', 'LWGEOM_estimated_extent'
+	LANGUAGE 'C' IMMUTABLE STRICT SECURITY DEFINER;
+
+-----------------------------------------------------------------------
+-- FIND_EXTENT( <schema name>, <table name>, <column name> )
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION find_extent(text,text,text) RETURNS box2d AS
+$$
+DECLARE
+	schemaname alias for $1;
+	tablename alias for $2;
+	columnname alias for $3;
+	myrec RECORD;
+
+BEGIN
+	FOR myrec IN EXECUTE 'SELECT extent("' || columnname || '") FROM "' || schemaname || '"."' || tablename || '"' LOOP
+		return myrec.extent;
+	END LOOP;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_find_extent(text,text,text) RETURNS box2d AS
+$$
+DECLARE
+	schemaname alias for $1;
+	tablename alias for $2;
+	columnname alias for $3;
+	myrec RECORD;
+
+BEGIN
+	FOR myrec IN EXECUTE 'SELECT extent("' || columnname || '") FROM "' || schemaname || '"."' || tablename || '"' LOOP
+		return myrec.extent;
+	END LOOP;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+
+-----------------------------------------------------------------------
+-- FIND_EXTENT( <table name>, <column name> )
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION find_extent(text,text) RETURNS box2d AS
+$$
+DECLARE
+	tablename alias for $1;
+	columnname alias for $2;
+	myrec RECORD;
+
+BEGIN
+	FOR myrec IN EXECUTE 'SELECT extent("' || columnname || '") FROM "' || tablename || '"' LOOP
+		return myrec.extent;
+	END LOOP;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_find_extent(text,text) RETURNS box2d AS
+$$
+DECLARE
+	tablename alias for $1;
+	columnname alias for $2;
+	myrec RECORD;
+
+BEGIN
+	FOR myrec IN EXECUTE 'SELECT extent("' || columnname || '") FROM "' || tablename || '"' LOOP
+		return myrec.extent;
+	END LOOP;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-------------------------------------------------------------------
+-- SPATIAL_REF_SYS
+-------------------------------------------------------------------
+CREATE TABLE spatial_ref_sys (
+	 srid integer not null primary key,
+	 auth_name varchar(256),
+	 auth_srid integer,
+	 srtext varchar(2048),
+	 proj4text varchar(2048)
+);
+
+-------------------------------------------------------------------
+-- GEOMETRY_COLUMNS
+-------------------------------------------------------------------
+CREATE TABLE geometry_columns (
+	f_table_catalog varchar(256) not null,
+	f_table_schema varchar(256) not null,
+	f_table_name varchar(256) not null,
+	f_geometry_column varchar(256) not null,
+	coord_dimension integer not null,
+	srid integer not null,
+	type varchar(30) not null,
+	CONSTRAINT geometry_columns_pk primary key (
+		f_table_catalog,
+		f_table_schema,
+		f_table_name,
+		f_geometry_column )
+) WITH OIDS;
+
+-----------------------------------------------------------------------
+-- RENAME_GEOMETRY_TABLE_CONSTRAINTS()
+-----------------------------------------------------------------------
+-- This function has been obsoleted for the difficulty in
+-- finding attribute on which the constraint is applied.
+-- AddGeometryColumn will name the constraints in a meaningful
+-- way, but nobody can rely on it since old postgis versions did
+-- not do that.
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION rename_geometry_table_constraints() RETURNS text
+AS
+$$
+SELECT 'rename_geometry_table_constraint() is obsoleted'::text
+$$
+LANGUAGE 'SQL' IMMUTABLE;
+
+-----------------------------------------------------------------------
+-- FIX_GEOMETRY_COLUMNS()
+-----------------------------------------------------------------------
+-- This function will:
+--
+--	o try to fix the schema of records with an integer one
+--		(for PG>=73)
+--
+--	o link records to system tables through attrelid and varattnum
+--		(for PG<75)
+--
+--	o delete all records for which no linking was possible
+--		(for PG<75)
+--
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION fix_geometry_columns() RETURNS text
+AS
+$$
+DECLARE
+	mislinked record;
+	result text;
+	linked integer;
+	deleted integer;
+	foundschema integer;
+BEGIN
+
+	-- Since 7.3 schema support has been added.
+	-- Previous postgis versions used to put the database name in
+	-- the schema column. This needs to be fixed, so we try to
+	-- set the correct schema for each geometry_colums record
+	-- looking at table, column, type and srid.
+	UPDATE geometry_columns SET f_table_schema = n.nspname
+		FROM pg_namespace n, pg_class c, pg_attribute a,
+			pg_constraint sridcheck, pg_constraint typecheck
+			WHERE ( f_table_schema is NULL
+		OR f_table_schema = ''
+			OR f_table_schema NOT IN (
+					SELECT nspname::varchar
+					FROM pg_namespace nn, pg_class cc, pg_attribute aa
+					WHERE cc.relnamespace = nn.oid
+					AND cc.relname = f_table_name::name
+					AND aa.attrelid = cc.oid
+					AND aa.attname = f_geometry_column::name))
+			AND f_table_name::name = c.relname
+			AND c.oid = a.attrelid
+			AND c.relnamespace = n.oid
+			AND f_geometry_column::name = a.attname
+
+			AND sridcheck.conrelid = c.oid
+		AND sridcheck.consrc LIKE '(srid(% = %)'
+			AND sridcheck.consrc ~ textcat(' = ', srid::text)
+
+			AND typecheck.conrelid = c.oid
+		AND typecheck.consrc LIKE
+		'((geometrytype(%) = ''%''::text) OR (% IS NULL))'
+			AND typecheck.consrc ~ textcat(' = ''', type::text)
+
+			AND NOT EXISTS (
+					SELECT oid FROM geometry_columns gc
+					WHERE c.relname::varchar = gc.f_table_name
+					AND n.nspname::varchar = gc.f_table_schema
+					AND a.attname::varchar = gc.f_geometry_column
+			);
+
+	GET DIAGNOSTICS foundschema = ROW_COUNT;
+
+	-- no linkage to system table needed
+	return 'fixed:'||foundschema::text;
+
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE;
+
+-----------------------------------------------------------------------
+-- POPULATE_GEOMETRY_COLUMNS()
+-----------------------------------------------------------------------
+-- Truncates and refills the geometry_columns table from all tables and
+-- views in the database that contain geometry columns. This function
+-- is a simple wrapper for populate_geometry_columns(oid).  In essence,
+-- this function ensures every geometry column in the database has the
+-- appropriate spatial contraints (for tables) and exists in the
+-- geometry_columns table.
+-- Availability: 1.4.0
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION populate_geometry_columns()
+	RETURNS text AS
+$$
+DECLARE
+	inserted    integer;
+	oldcount    integer;
+	probed      integer;
+	stale       integer;
+	gcs         RECORD;
+	gc          RECORD;
+	gsrid       integer;
+	gndims      integer;
+	gtype       text;
+	query       text;
+	gc_is_valid boolean;
+
+BEGIN
+	SELECT count(*) INTO oldcount FROM geometry_columns;
+	inserted := 0;
+
+	EXECUTE 'TRUNCATE geometry_columns';
+
+	-- Count the number of geometry columns in all tables and views
+	SELECT count(DISTINCT c.oid) INTO probed
+	FROM pg_class c,
+		 pg_attribute a,
+		 pg_type t,
+		 pg_namespace n
+	WHERE (c.relkind = 'r' OR c.relkind = 'v')
+	AND t.typname = 'geometry'
+	AND a.attisdropped = false
+	AND a.atttypid = t.oid
+	AND a.attrelid = c.oid
+	AND c.relnamespace = n.oid
+	AND n.nspname NOT ILIKE 'pg_temp%';
+
+	-- Iterate through all non-dropped geometry columns
+	RAISE DEBUG 'Processing Tables.....';
+
+	FOR gcs IN
+	SELECT DISTINCT ON (c.oid) c.oid, n.nspname, c.relname
+		FROM pg_class c,
+			 pg_attribute a,
+			 pg_type t,
+			 pg_namespace n
+		WHERE c.relkind = 'r'
+		AND t.typname = 'geometry'
+		AND a.attisdropped = false
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND n.nspname NOT ILIKE 'pg_temp%'
+	LOOP
+
+	inserted := inserted + populate_geometry_columns(gcs.oid);
+	END LOOP;
+
+	-- Add views to geometry columns table
+	RAISE DEBUG 'Processing Views.....';
+	FOR gcs IN
+	SELECT DISTINCT ON (c.oid) c.oid, n.nspname, c.relname
+		FROM pg_class c,
+			 pg_attribute a,
+			 pg_type t,
+			 pg_namespace n
+		WHERE c.relkind = 'v'
+		AND t.typname = 'geometry'
+		AND a.attisdropped = false
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+	LOOP
+
+	inserted := inserted + populate_geometry_columns(gcs.oid);
+	END LOOP;
+
+	IF oldcount > inserted THEN
+	stale = oldcount-inserted;
+	ELSE
+	stale = 0;
+	END IF;
+
+	RETURN 'probed:' ||probed|| ' inserted:'||inserted|| ' conflicts:'||probed-inserted|| ' deleted:'||stale;
+END
+
+$$
+LANGUAGE 'plpgsql' VOLATILE;
+
+-----------------------------------------------------------------------
+-- POPULATE_GEOMETRY_COLUMNS(tbl_oid oid)
+-----------------------------------------------------------------------
+-- DELETEs from and reINSERTs into the geometry_columns table all entries
+-- associated with the oid of a particular table or view.
+--
+-- If the provided oid is for a table, this function tries to determine
+-- the srid, dimension, and geometry type of the all geometries
+-- in the table, adding contraints as necessary to the table.  If
+-- successful, an appropriate row is inserted into the geometry_columns
+-- table, otherwise, the exception is caught and an error notice is
+-- raised describing the problem. (This is so the wrapper function
+-- populate_geometry_columns() can apply spatial constraints to all
+-- geometry columns across an entire database at once without erroring
+-- out)
+--
+-- If the provided oid is for a view, as with a table oid, this function
+-- tries to determine the srid, dimension, and type of all the geometries
+-- in the view, inserting appropriate entries into the geometry_columns
+-- table.
+-- Availability: 1.4.0
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION populate_geometry_columns(tbl_oid oid)
+	RETURNS integer AS
+$$
+DECLARE
+	gcs         RECORD;
+	gc          RECORD;
+	gsrid       integer;
+	gndims      integer;
+	gtype       text;
+	query       text;
+	gc_is_valid boolean;
+	inserted    integer;
+
+BEGIN
+	inserted := 0;
+
+	-- Iterate through all geometry columns in this table
+	FOR gcs IN
+	SELECT n.nspname, c.relname, a.attname
+		FROM pg_class c,
+			 pg_attribute a,
+			 pg_type t,
+			 pg_namespace n
+		WHERE c.relkind = 'r'
+		AND t.typname = 'geometry'
+		AND a.attisdropped = false
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND n.nspname NOT ILIKE 'pg_temp%'
+		AND c.oid = tbl_oid
+	LOOP
+
+	RAISE DEBUG 'Processing table %.%.%', gcs.nspname, gcs.relname, gcs.attname;
+
+	DELETE FROM geometry_columns
+	  WHERE f_table_schema = quote_ident(gcs.nspname)
+	  AND f_table_name = quote_ident(gcs.relname)
+	  AND f_geometry_column = quote_ident(gcs.attname);
+
+	gc_is_valid := true;
+
+	-- Try to find srid check from system tables (pg_constraint)
+	gsrid :=
+		(SELECT replace(replace(split_part(s.consrc, ' = ', 2), ')', ''), '(', '')
+		 FROM pg_class c, pg_namespace n, pg_attribute a, pg_constraint s
+		 WHERE n.nspname = gcs.nspname
+		 AND c.relname = gcs.relname
+		 AND a.attname = gcs.attname
+		 AND a.attrelid = c.oid
+		 AND s.connamespace = n.oid
+		 AND s.conrelid = c.oid
+		 AND a.attnum = ANY (s.conkey)
+		 AND s.consrc LIKE '%srid(% = %');
+	IF (gsrid IS NULL) THEN
+		-- Try to find srid from the geometry itself
+		EXECUTE 'SELECT srid(' || quote_ident(gcs.attname) || ')
+				 FROM ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gsrid := gc.srid;
+
+		-- Try to apply srid check to column
+		IF (gsrid IS NOT NULL) THEN
+			BEGIN
+				EXECUTE 'ALTER TABLE ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+						 ADD CONSTRAINT ' || quote_ident('enforce_srid_' || gcs.attname) || '
+						 CHECK (srid(' || quote_ident(gcs.attname) || ') = ' || gsrid || ')';
+			EXCEPTION
+				WHEN check_violation THEN
+					RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not apply constraint CHECK (srid(%) = %)', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname), quote_ident(gcs.attname), gsrid;
+					gc_is_valid := false;
+			END;
+		END IF;
+	END IF;
+
+	-- Try to find ndims check from system tables (pg_constraint)
+	gndims :=
+		(SELECT replace(split_part(s.consrc, ' = ', 2), ')', '')
+		 FROM pg_class c, pg_namespace n, pg_attribute a, pg_constraint s
+		 WHERE n.nspname = gcs.nspname
+		 AND c.relname = gcs.relname
+		 AND a.attname = gcs.attname
+		 AND a.attrelid = c.oid
+		 AND s.connamespace = n.oid
+		 AND s.conrelid = c.oid
+		 AND a.attnum = ANY (s.conkey)
+		 AND s.consrc LIKE '%ndims(% = %');
+	IF (gndims IS NULL) THEN
+		-- Try to find ndims from the geometry itself
+		EXECUTE 'SELECT ndims(' || quote_ident(gcs.attname) || ')
+				 FROM ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gndims := gc.ndims;
+
+		-- Try to apply ndims check to column
+		IF (gndims IS NOT NULL) THEN
+			BEGIN
+				EXECUTE 'ALTER TABLE ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+						 ADD CONSTRAINT ' || quote_ident('enforce_dims_' || gcs.attname) || '
+						 CHECK (ndims(' || quote_ident(gcs.attname) || ') = '||gndims||')';
+			EXCEPTION
+				WHEN check_violation THEN
+					RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not apply constraint CHECK (ndims(%) = %)', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname), quote_ident(gcs.attname), gndims;
+					gc_is_valid := false;
+			END;
+		END IF;
+	END IF;
+
+	-- Try to find geotype check from system tables (pg_constraint)
+	gtype :=
+		(SELECT replace(split_part(s.consrc, '''', 2), ')', '')
+		 FROM pg_class c, pg_namespace n, pg_attribute a, pg_constraint s
+		 WHERE n.nspname = gcs.nspname
+		 AND c.relname = gcs.relname
+		 AND a.attname = gcs.attname
+		 AND a.attrelid = c.oid
+		 AND s.connamespace = n.oid
+		 AND s.conrelid = c.oid
+		 AND a.attnum = ANY (s.conkey)
+		 AND s.consrc LIKE '%geometrytype(% = %');
+	IF (gtype IS NULL) THEN
+		-- Try to find geotype from the geometry itself
+		EXECUTE 'SELECT geometrytype(' || quote_ident(gcs.attname) || ')
+				 FROM ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gtype := gc.geometrytype;
+		--IF (gtype IS NULL) THEN
+		--    gtype := 'GEOMETRY';
+		--END IF;
+
+		-- Try to apply geometrytype check to column
+		IF (gtype IS NOT NULL) THEN
+			BEGIN
+				EXECUTE 'ALTER TABLE ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				ADD CONSTRAINT ' || quote_ident('enforce_geotype_' || gcs.attname) || '
+				CHECK ((geometrytype(' || quote_ident(gcs.attname) || ') = ' || quote_literal(gtype) || ') OR (' || quote_ident(gcs.attname) || ' IS NULL))';
+			EXCEPTION
+				WHEN check_violation THEN
+					-- No geometry check can be applied. This column contains a number of geometry types.
+					RAISE WARNING 'Could not add geometry type check (%) to table column: %.%.%', gtype, quote_ident(gcs.nspname),quote_ident(gcs.relname),quote_ident(gcs.attname);
+			END;
+		END IF;
+	END IF;
+
+	IF (gsrid IS NULL) THEN
+		RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine the srid', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+	ELSIF (gndims IS NULL) THEN
+		RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine the number of dimensions', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+	ELSIF (gtype IS NULL) THEN
+		RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine the geometry type', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+	ELSE
+		-- Only insert into geometry_columns if table constraints could be applied.
+		IF (gc_is_valid) THEN
+			INSERT INTO geometry_columns (f_table_catalog,f_table_schema, f_table_name, f_geometry_column, coord_dimension, srid, type)
+			VALUES ('', gcs.nspname, gcs.relname, gcs.attname, gndims, gsrid, gtype);
+			inserted := inserted + 1;
+		END IF;
+	END IF;
+	END LOOP;
+
+	-- Add views to geometry columns table
+	FOR gcs IN
+	SELECT n.nspname, c.relname, a.attname
+		FROM pg_class c,
+			 pg_attribute a,
+			 pg_type t,
+			 pg_namespace n
+		WHERE c.relkind = 'v'
+		AND t.typname = 'geometry'
+		AND a.attisdropped = false
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND n.nspname NOT ILIKE 'pg_temp%'
+		AND c.oid = tbl_oid
+	LOOP
+		RAISE DEBUG 'Processing view %.%.%', gcs.nspname, gcs.relname, gcs.attname;
+
+		EXECUTE 'SELECT ndims(' || quote_ident(gcs.attname) || ')
+				 FROM ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gndims := gc.ndims;
+
+		EXECUTE 'SELECT srid(' || quote_ident(gcs.attname) || ')
+				 FROM ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gsrid := gc.srid;
+
+		EXECUTE 'SELECT geometrytype(' || quote_ident(gcs.attname) || ')
+				 FROM ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gtype := gc.geometrytype;
+
+		IF (gndims IS NULL) THEN
+			RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine ndims', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+		ELSIF (gsrid IS NULL) THEN
+			RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine srid', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+		ELSIF (gtype IS NULL) THEN
+			RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine gtype', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+		ELSE
+			query := 'INSERT INTO geometry_columns (f_table_catalog,f_table_schema, f_table_name, f_geometry_column, coord_dimension, srid, type) ' ||
+					 'VALUES ('''', ' || quote_literal(gcs.nspname) || ',' || quote_literal(gcs.relname) || ',' || quote_literal(gcs.attname) || ',' || gndims || ',' || gsrid || ',' || quote_literal(gtype) || ')';
+			EXECUTE query;
+			inserted := inserted + 1;
+		END IF;
+	END LOOP;
+
+	RETURN inserted;
+END
+
+$$
+LANGUAGE 'plpgsql' VOLATILE;
+
+
+-----------------------------------------------------------------------
+-- PROBE_GEOMETRY_COLUMNS()
+-----------------------------------------------------------------------
+-- Fill the geometry_columns table with values probed from the system
+-- catalogues. This is done by simply looking up constraints previously
+-- added to a geometry column. If geometry constraints are missing, no
+-- attempt is made to add the necessary constraints to the geometry
+-- column, nor is it recorded in the geometry_columns table.
+-- 3d flag cannot be probed, it defaults to 2
+--
+-- Note that bogus records already in geometry_columns are not
+-- overridden (a check for schema.table.column is performed), so
+-- to have a fresh probe backup your geometry_columns, delete from
+-- it and probe.
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION probe_geometry_columns() RETURNS text AS
+$$
+DECLARE
+	inserted integer;
+	oldcount integer;
+	probed integer;
+	stale integer;
+BEGIN
+
+	SELECT count(*) INTO oldcount FROM geometry_columns;
+
+	SELECT count(*) INTO probed
+		FROM pg_class c, pg_attribute a, pg_type t,
+			pg_namespace n,
+			pg_constraint sridcheck, pg_constraint typecheck
+
+		WHERE t.typname = 'geometry'
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND sridcheck.connamespace = n.oid
+		AND typecheck.connamespace = n.oid
+		AND sridcheck.conrelid = c.oid
+		AND sridcheck.consrc LIKE '(srid('||a.attname||') = %)'
+		AND typecheck.conrelid = c.oid
+		AND typecheck.consrc LIKE
+		'((geometrytype('||a.attname||') = ''%''::text) OR (% IS NULL))'
+		;
+
+	INSERT INTO geometry_columns SELECT
+		''::varchar as f_table_catalogue,
+		n.nspname::varchar as f_table_schema,
+		c.relname::varchar as f_table_name,
+		a.attname::varchar as f_geometry_column,
+		2 as coord_dimension,
+		trim(both  ' =)' from
+			replace(replace(split_part(
+				sridcheck.consrc, ' = ', 2), ')', ''), '(', ''))::integer AS srid,
+		trim(both ' =)''' from substr(typecheck.consrc,
+			strpos(typecheck.consrc, '='),
+			strpos(typecheck.consrc, '::')-
+			strpos(typecheck.consrc, '=')
+			))::varchar as type
+		FROM pg_class c, pg_attribute a, pg_type t,
+			pg_namespace n,
+			pg_constraint sridcheck, pg_constraint typecheck
+		WHERE t.typname = 'geometry'
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND sridcheck.connamespace = n.oid
+		AND typecheck.connamespace = n.oid
+		AND sridcheck.conrelid = c.oid
+		AND sridcheck.consrc LIKE '(st_srid('||a.attname||') = %)'
+		AND typecheck.conrelid = c.oid
+		AND typecheck.consrc LIKE
+		'((geometrytype('||a.attname||') = ''%''::text) OR (% IS NULL))'
+
+			AND NOT EXISTS (
+					SELECT oid FROM geometry_columns gc
+					WHERE c.relname::varchar = gc.f_table_name
+					AND n.nspname::varchar = gc.f_table_schema
+					AND a.attname::varchar = gc.f_geometry_column
+			);
+
+	GET DIAGNOSTICS inserted = ROW_COUNT;
+
+	IF oldcount > probed THEN
+		stale = oldcount-probed;
+	ELSE
+		stale = 0;
+	END IF;
+
+	RETURN 'probed:'||probed::text||
+		' inserted:'||inserted::text||
+		' conflicts:'||(probed-inserted)::text||
+		' stale:'||stale::text;
+END
+
+$$
+LANGUAGE 'plpgsql' VOLATILE;
+
+-----------------------------------------------------------------------
+-- ADDGEOMETRYCOLUMN
+--   <catalogue>, <schema>, <table>, <column>, <srid>, <type>, <dim>
+-----------------------------------------------------------------------
+--
+-- Type can be one of GEOMETRY, GEOMETRYCOLLECTION, POINT, MULTIPOINT, POLYGON,
+-- MULTIPOLYGON, LINESTRING, or MULTILINESTRING.
+--
+-- Geometry types (except GEOMETRY) are checked for consistency using a CHECK constraint.
+-- Uses an ALTER TABLE command to add the geometry column to the table.
+-- Addes a row to geometry_columns.
+-- Addes a constraint on the table that all the geometries MUST have the same
+-- SRID. Checks the coord_dimension to make sure its between 0 and 3.
+-- Should also check the precision grid (future expansion).
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION AddGeometryColumn(varchar,varchar,varchar,varchar,integer,varchar,integer)
+	RETURNS text
+	AS
+$$
+DECLARE
+	catalog_name alias for $1;
+	schema_name alias for $2;
+	table_name alias for $3;
+	column_name alias for $4;
+	new_srid alias for $5;
+	new_type alias for $6;
+	new_dim alias for $7;
+	rec RECORD;
+	sr varchar;
+	real_schema name;
+	sql text;
+
+BEGIN
+
+	-- Verify geometry type
+	IF ( NOT ( (new_type = 'GEOMETRY') OR
+			   (new_type = 'GEOMETRYCOLLECTION') OR
+			   (new_type = 'POINT') OR
+			   (new_type = 'MULTIPOINT') OR
+			   (new_type = 'POLYGON') OR
+			   (new_type = 'MULTIPOLYGON') OR
+			   (new_type = 'LINESTRING') OR
+			   (new_type = 'MULTILINESTRING') OR
+			   (new_type = 'GEOMETRYCOLLECTIONM') OR
+			   (new_type = 'POINTM') OR
+			   (new_type = 'MULTIPOINTM') OR
+			   (new_type = 'POLYGONM') OR
+			   (new_type = 'MULTIPOLYGONM') OR
+			   (new_type = 'LINESTRINGM') OR
+			   (new_type = 'MULTILINESTRINGM') OR
+			   (new_type = 'CIRCULARSTRING') OR
+			   (new_type = 'CIRCULARSTRINGM') OR
+			   (new_type = 'COMPOUNDCURVE') OR
+			   (new_type = 'COMPOUNDCURVEM') OR
+			   (new_type = 'CURVEPOLYGON') OR
+			   (new_type = 'CURVEPOLYGONM') OR
+			   (new_type = 'MULTICURVE') OR
+			   (new_type = 'MULTICURVEM') OR
+			   (new_type = 'MULTISURFACE') OR
+			   (new_type = 'MULTISURFACEM')) )
+	THEN
+		RAISE EXCEPTION 'Invalid type name - valid ones are:
+	POINT, MULTIPOINT,
+	LINESTRING, MULTILINESTRING,
+	POLYGON, MULTIPOLYGON,
+	CIRCULARSTRING, COMPOUNDCURVE, MULTICURVE,
+	CURVEPOLYGON, MULTISURFACE,
+	GEOMETRY, GEOMETRYCOLLECTION,
+	POINTM, MULTIPOINTM,
+	LINESTRINGM, MULTILINESTRINGM,
+	POLYGONM, MULTIPOLYGONM,
+	CIRCULARSTRINGM, COMPOUNDCURVEM, MULTICURVEM
+	CURVEPOLYGONM, MULTISURFACEM,
+	or GEOMETRYCOLLECTIONM';
+		RETURN 'fail';
+	END IF;
+
+
+	-- Verify dimension
+	IF ( (new_dim >4) OR (new_dim <0) ) THEN
+		RAISE EXCEPTION 'invalid dimension';
+		RETURN 'fail';
+	END IF;
+
+	IF ( (new_type LIKE '%M') AND (new_dim!=3) ) THEN
+		RAISE EXCEPTION 'TypeM needs 3 dimensions';
+		RETURN 'fail';
+	END IF;
+
+
+	-- Verify SRID
+	IF ( new_srid != -1 ) THEN
+		SELECT SRID INTO sr FROM spatial_ref_sys WHERE SRID = new_srid;
+		IF NOT FOUND THEN
+			RAISE EXCEPTION 'AddGeometryColumns() - invalid SRID';
+			RETURN 'fail';
+		END IF;
+	END IF;
+
+
+	-- Verify schema
+	IF ( schema_name IS NOT NULL AND schema_name != '' ) THEN
+		sql := 'SELECT nspname FROM pg_namespace ' ||
+			'WHERE text(nspname) = ' || quote_literal(schema_name) ||
+			'LIMIT 1';
+		RAISE DEBUG '%', sql;
+		EXECUTE sql INTO real_schema;
+
+		IF ( real_schema IS NULL ) THEN
+			RAISE EXCEPTION 'Schema % is not a valid schemaname', quote_literal(schema_name);
+			RETURN 'fail';
+		END IF;
+	END IF;
+
+	IF ( real_schema IS NULL ) THEN
+		RAISE DEBUG 'Detecting schema';
+		sql := 'SELECT n.nspname AS schemaname ' ||
+			'FROM pg_catalog.pg_class c ' ||
+			  'JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace ' ||
+			'WHERE c.relkind = ' || quote_literal('r') ||
+			' AND n.nspname NOT IN (' || quote_literal('pg_catalog') || ', ' || quote_literal('pg_toast') || ')' ||
+			' AND pg_catalog.pg_table_is_visible(c.oid)' ||
+			' AND c.relname = ' || quote_literal(table_name);
+		RAISE DEBUG '%', sql;
+		EXECUTE sql INTO real_schema;
+
+		IF ( real_schema IS NULL ) THEN
+			RAISE EXCEPTION 'Table % does not occur in the search_path', quote_literal(table_name);
+			RETURN 'fail';
+		END IF;
+	END IF;
+
+
+	-- Add geometry column to table
+	sql := 'ALTER TABLE ' ||
+		quote_ident(real_schema) || '.' || quote_ident(table_name)
+		|| ' ADD COLUMN ' || quote_ident(column_name) ||
+		' geometry ';
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+
+	-- Delete stale record in geometry_columns (if any)
+	sql := 'DELETE FROM geometry_columns WHERE
+		f_table_catalog = ' || quote_literal('') ||
+		' AND f_table_schema = ' ||
+		quote_literal(real_schema) ||
+		' AND f_table_name = ' || quote_literal(table_name) ||
+		' AND f_geometry_column = ' || quote_literal(column_name);
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+
+	-- Add record in geometry_columns
+	sql := 'INSERT INTO geometry_columns (f_table_catalog,f_table_schema,f_table_name,' ||
+										  'f_geometry_column,coord_dimension,srid,type)' ||
+		' VALUES (' ||
+		quote_literal('') || ',' ||
+		quote_literal(real_schema) || ',' ||
+		quote_literal(table_name) || ',' ||
+		quote_literal(column_name) || ',' ||
+		new_dim::text || ',' ||
+		new_srid::text || ',' ||
+		quote_literal(new_type) || ')';
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+
+	-- Add table CHECKs
+	sql := 'ALTER TABLE ' ||
+		quote_ident(real_schema) || '.' || quote_ident(table_name)
+		|| ' ADD CONSTRAINT '
+		|| quote_ident('enforce_srid_' || column_name)
+		|| ' CHECK (ST_SRID(' || quote_ident(column_name) ||
+		') = ' || new_srid::text || ')' ;
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+	sql := 'ALTER TABLE ' ||
+		quote_ident(real_schema) || '.' || quote_ident(table_name)
+		|| ' ADD CONSTRAINT '
+		|| quote_ident('enforce_dims_' || column_name)
+		|| ' CHECK (ST_NDims(' || quote_ident(column_name) ||
+		') = ' || new_dim::text || ')' ;
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+	IF ( NOT (new_type = 'GEOMETRY')) THEN
+		sql := 'ALTER TABLE ' ||
+			quote_ident(real_schema) || '.' || quote_ident(table_name) || ' ADD CONSTRAINT ' ||
+			quote_ident('enforce_geotype_' || column_name) ||
+			' CHECK (GeometryType(' ||
+			quote_ident(column_name) || ')=' ||
+			quote_literal(new_type) || ' OR (' ||
+			quote_ident(column_name) || ') is null)';
+		RAISE DEBUG '%', sql;
+		EXECUTE sql;
+	END IF;
+
+	RETURN
+		real_schema || '.' ||
+		table_name || '.' || column_name ||
+		' SRID:' || new_srid::text ||
+		' TYPE:' || new_type ||
+		' DIMS:' || new_dim::text || ' ';
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+----------------------------------------------------------------------------
+-- ADDGEOMETRYCOLUMN ( <schema>, <table>, <column>, <srid>, <type>, <dim> )
+----------------------------------------------------------------------------
+--
+-- This is a wrapper to the real AddGeometryColumn, for use
+-- when catalogue is undefined
+--
+----------------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION AddGeometryColumn(varchar,varchar,varchar,integer,varchar,integer) RETURNS text AS $$
+DECLARE
+	ret  text;
+BEGIN
+	SELECT AddGeometryColumn('',$1,$2,$3,$4,$5,$6) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' STABLE STRICT;
+
+----------------------------------------------------------------------------
+-- ADDGEOMETRYCOLUMN ( <table>, <column>, <srid>, <type>, <dim> )
+----------------------------------------------------------------------------
+--
+-- This is a wrapper to the real AddGeometryColumn, for use
+-- when catalogue and schema are undefined
+--
+----------------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION AddGeometryColumn(varchar,varchar,integer,varchar,integer) RETURNS text AS $$
+DECLARE
+	ret  text;
+BEGIN
+	SELECT AddGeometryColumn('','',$1,$2,$3,$4,$5) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYCOLUMN
+--   <catalogue>, <schema>, <table>, <column>
+-----------------------------------------------------------------------
+--
+-- Removes geometry column reference from geometry_columns table.
+-- Drops the column with pgsql >= 73.
+-- Make some silly enforcements on it for pgsql < 73
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryColumn(varchar, varchar,varchar,varchar)
+	RETURNS text
+	AS
+$$
+DECLARE
+	catalog_name alias for $1;
+	schema_name alias for $2;
+	table_name alias for $3;
+	column_name alias for $4;
+	myrec RECORD;
+	okay boolean;
+	real_schema name;
+
+BEGIN
+
+
+	-- Find, check or fix schema_name
+	IF ( schema_name != '' ) THEN
+		okay = 'f';
+
+		FOR myrec IN SELECT nspname FROM pg_namespace WHERE text(nspname) = schema_name LOOP
+			okay := 't';
+		END LOOP;
+
+		IF ( okay <> 't' ) THEN
+			RAISE NOTICE 'Invalid schema name - using current_schema()';
+			SELECT current_schema() into real_schema;
+		ELSE
+			real_schema = schema_name;
+		END IF;
+	ELSE
+		SELECT current_schema() into real_schema;
+	END IF;
+
+	-- Find out if the column is in the geometry_columns table
+	okay = 'f';
+	FOR myrec IN SELECT * from geometry_columns where f_table_schema = text(real_schema) and f_table_name = table_name and f_geometry_column = column_name LOOP
+		okay := 't';
+	END LOOP;
+	IF (okay <> 't') THEN
+		RAISE EXCEPTION 'column not found in geometry_columns table';
+		RETURN 'f';
+	END IF;
+
+	-- Remove ref from geometry_columns table
+	EXECUTE 'delete from geometry_columns where f_table_schema = ' ||
+		quote_literal(real_schema) || ' and f_table_name = ' ||
+		quote_literal(table_name)  || ' and f_geometry_column = ' ||
+		quote_literal(column_name);
+
+	-- Remove table column
+	EXECUTE 'ALTER TABLE ' || quote_ident(real_schema) || '.' ||
+		quote_ident(table_name) || ' DROP COLUMN ' ||
+		quote_ident(column_name);
+
+	RETURN real_schema || '.' || table_name || '.' || column_name ||' effectively removed.';
+
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYCOLUMN
+--   <schema>, <table>, <column>
+-----------------------------------------------------------------------
+--
+-- This is a wrapper to the real DropGeometryColumn, for use
+-- when catalogue is undefined
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryColumn(varchar,varchar,varchar)
+	RETURNS text
+	AS
+$$
+DECLARE
+	ret text;
+BEGIN
+	SELECT DropGeometryColumn('',$1,$2,$3) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYCOLUMN
+--   <table>, <column>
+-----------------------------------------------------------------------
+--
+-- This is a wrapper to the real DropGeometryColumn, for use
+-- when catalogue and schema is undefined.
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryColumn(varchar,varchar)
+	RETURNS text
+	AS
+$$
+DECLARE
+	ret text;
+BEGIN
+	SELECT DropGeometryColumn('','',$1,$2) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYTABLE
+--   <catalogue>, <schema>, <table>
+-----------------------------------------------------------------------
+--
+-- Drop a table and all its references in geometry_columns
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryTable(varchar, varchar,varchar)
+	RETURNS text
+	AS
+$$
+DECLARE
+	catalog_name alias for $1;
+	schema_name alias for $2;
+	table_name alias for $3;
+	real_schema name;
+
+BEGIN
+
+	IF ( schema_name = '' ) THEN
+		SELECT current_schema() into real_schema;
+	ELSE
+		real_schema = schema_name;
+	END IF;
+
+	-- Remove refs from geometry_columns table
+	EXECUTE 'DELETE FROM geometry_columns WHERE ' ||
+		'f_table_schema = ' || quote_literal(real_schema) ||
+		' AND ' ||
+		' f_table_name = ' || quote_literal(table_name);
+
+	-- Remove table
+	EXECUTE 'DROP TABLE '
+		|| quote_ident(real_schema) || '.' ||
+		quote_ident(table_name);
+
+	RETURN
+		real_schema || '.' ||
+		table_name ||' dropped.';
+
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYTABLE
+--   <schema>, <table>
+-----------------------------------------------------------------------
+--
+-- Drop a table and all its references in geometry_columns
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryTable(varchar,varchar) RETURNS text AS
+$$ SELECT DropGeometryTable('',$1,$2) $$
+LANGUAGE 'sql' WITH (isstrict);
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYTABLE
+--   <table>
+-----------------------------------------------------------------------
+--
+-- Drop a table and all its references in geometry_columns
+-- For PG>=73 use current_schema()
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryTable(varchar) RETURNS text AS
+$$ SELECT DropGeometryTable('','',$1) $$
+LANGUAGE 'sql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- UPDATEGEOMETRYSRID
+--   <catalogue>, <schema>, <table>, <column>, <srid>
+-----------------------------------------------------------------------
+--
+-- Change SRID of all features in a spatially-enabled table
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION UpdateGeometrySRID(varchar,varchar,varchar,varchar,integer)
+	RETURNS text
+	AS
+$$
+DECLARE
+	catalog_name alias for $1;
+	schema_name alias for $2;
+	table_name alias for $3;
+	column_name alias for $4;
+	new_srid alias for $5;
+	myrec RECORD;
+	okay boolean;
+	cname varchar;
+	real_schema name;
+
+BEGIN
+
+
+	-- Find, check or fix schema_name
+	IF ( schema_name != '' ) THEN
+		okay = 'f';
+
+		FOR myrec IN SELECT nspname FROM pg_namespace WHERE text(nspname) = schema_name LOOP
+			okay := 't';
+		END LOOP;
+
+		IF ( okay <> 't' ) THEN
+			RAISE EXCEPTION 'Invalid schema name';
+		ELSE
+			real_schema = schema_name;
+		END IF;
+	ELSE
+		SELECT INTO real_schema current_schema()::text;
+	END IF;
+
+	-- Find out if the column is in the geometry_columns table
+	okay = 'f';
+	FOR myrec IN SELECT * from geometry_columns where f_table_schema = text(real_schema) and f_table_name = table_name and f_geometry_column = column_name LOOP
+		okay := 't';
+	END LOOP;
+	IF (okay <> 't') THEN
+		RAISE EXCEPTION 'column not found in geometry_columns table';
+		RETURN 'f';
+	END IF;
+
+	-- Update ref from geometry_columns table
+	EXECUTE 'UPDATE geometry_columns SET SRID = ' || new_srid::text ||
+		' where f_table_schema = ' ||
+		quote_literal(real_schema) || ' and f_table_name = ' ||
+		quote_literal(table_name)  || ' and f_geometry_column = ' ||
+		quote_literal(column_name);
+
+	-- Make up constraint name
+	cname = 'enforce_srid_'  || column_name;
+
+	-- Drop enforce_srid constraint
+	EXECUTE 'ALTER TABLE ' || quote_ident(real_schema) ||
+		'.' || quote_ident(table_name) ||
+		' DROP constraint ' || quote_ident(cname);
+
+	-- Update geometries SRID
+	EXECUTE 'UPDATE ' || quote_ident(real_schema) ||
+		'.' || quote_ident(table_name) ||
+		' SET ' || quote_ident(column_name) ||
+		' = setSRID(' || quote_ident(column_name) ||
+		', ' || new_srid::text || ')';
+
+	-- Reset enforce_srid constraint
+	EXECUTE 'ALTER TABLE ' || quote_ident(real_schema) ||
+		'.' || quote_ident(table_name) ||
+		' ADD constraint ' || quote_ident(cname) ||
+		' CHECK (srid(' || quote_ident(column_name) ||
+		') = ' || new_srid::text || ')';
+
+	RETURN real_schema || '.' || table_name || '.' || column_name ||' SRID changed to ' || new_srid::text;
+
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- UPDATEGEOMETRYSRID
+--   <schema>, <table>, <column>, <srid>
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION UpdateGeometrySRID(varchar,varchar,varchar,integer)
+	RETURNS text
+	AS $$
+DECLARE
+	ret  text;
+BEGIN
+	SELECT UpdateGeometrySRID('',$1,$2,$3,$4) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- UPDATEGEOMETRYSRID
+--   <table>, <column>, <srid>
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION UpdateGeometrySRID(varchar,varchar,integer)
+	RETURNS text
+	AS $$
+DECLARE
+	ret  text;
+BEGIN
+	SELECT UpdateGeometrySRID('','',$1,$2,$3) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- FIND_SRID( <schema>, <table>, <geom col> )
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION find_srid(varchar,varchar,varchar) RETURNS int4 AS
+$$
+DECLARE
+	schem text;
+	tabl text;
+	sr int4;
+BEGIN
+	IF $1 IS NULL THEN
+	  RAISE EXCEPTION 'find_srid() - schema is NULL!';
+	END IF;
+	IF $2 IS NULL THEN
+	  RAISE EXCEPTION 'find_srid() - table name is NULL!';
+	END IF;
+	IF $3 IS NULL THEN
+	  RAISE EXCEPTION 'find_srid() - column name is NULL!';
+	END IF;
+	schem = $1;
+	tabl = $2;
+-- if the table contains a . and the schema is empty
+-- split the table into a schema and a table
+-- otherwise drop through to default behavior
+	IF ( schem = '' and tabl LIKE '%.%' ) THEN
+	 schem = substr(tabl,1,strpos(tabl,'.')-1);
+	 tabl = substr(tabl,length(schem)+2);
+	ELSE
+	 schem = schem || '%';
+	END IF;
+
+	select SRID into sr from geometry_columns where f_table_schema like schem and f_table_name = tabl and f_geometry_column = $3;
+	IF NOT FOUND THEN
+	   RAISE EXCEPTION 'find_srid() - couldnt find the corresponding SRID - is the geometry registered in the GEOMETRY_COLUMNS table?  Is there an uppercase/lowercase missmatch?';
+	END IF;
+	return sr;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+
+---------------------------------------------------------------
+-- PROJ support
+---------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION get_proj4_from_srid(integer) RETURNS text AS
+$$
+BEGIN
+	RETURN proj4text::text FROM spatial_ref_sys WHERE srid= $1;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION postgis_transform_geometry(geometry,text,text,int)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','transform_geom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION transform(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','transform'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: transform(geometry,integer)
+CREATE OR REPLACE FUNCTION ST_Transform(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','transform'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+
+-----------------------------------------------------------------------
+-- POSTGIS_VERSION()
+-----------------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION postgis_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_proj_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+--
+-- IMPORTANT:
+-- Starting at 1.1.0 this function is used by postgis_proc_upgrade.pl
+-- to extract version of postgis being installed.
+-- Do not modify this w/out also changing postgis_proc_upgrade.pl
+--
+CREATE OR REPLACE FUNCTION postgis_scripts_installed() RETURNS text
+	AS 'SELECT ''1.5 r5385''::text AS version'
+	LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_lib_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE; -- a new lib will require a new session
+
+-- NOTE: starting at 1.1.0 this is the same of postgis_lib_version()
+CREATE OR REPLACE FUNCTION postgis_scripts_released() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_uses_stats() RETURNS bool
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_geos_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_libxml_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_scripts_build_date() RETURNS text
+	AS 'SELECT ''2010-03-11 19:15:17''::text AS version'
+	LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_lib_build_date() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+
+
+CREATE OR REPLACE FUNCTION postgis_full_version() RETURNS text
+AS $$
+DECLARE
+	libver text;
+	projver text;
+	geosver text;
+	libxmlver text;
+	usestats bool;
+	dbproc text;
+	relproc text;
+	fullver text;
+BEGIN
+	SELECT postgis_lib_version() INTO libver;
+	SELECT postgis_proj_version() INTO projver;
+	SELECT postgis_geos_version() INTO geosver;
+	SELECT postgis_libxml_version() INTO libxmlver;
+	SELECT postgis_uses_stats() INTO usestats;
+	SELECT postgis_scripts_installed() INTO dbproc;
+	SELECT postgis_scripts_released() INTO relproc;
+
+	fullver = 'POSTGIS="' || libver || '"';
+
+	IF  geosver IS NOT NULL THEN
+		fullver = fullver || ' GEOS="' || geosver || '"';
+	END IF;
+
+	IF  projver IS NOT NULL THEN
+		fullver = fullver || ' PROJ="' || projver || '"';
+	END IF;
+
+	IF  libxmlver IS NOT NULL THEN
+		fullver = fullver || ' LIBXML="' || libxmlver || '"';
+	END IF;
+
+	IF usestats THEN
+		fullver = fullver || ' USE_STATS';
+	END IF;
+
+	-- fullver = fullver || ' DBPROC="' || dbproc || '"';
+	-- fullver = fullver || ' RELPROC="' || relproc || '"';
+
+	IF dbproc != relproc THEN
+		fullver = fullver || ' (procs from ' || dbproc || ' need upgrade)';
+	END IF;
+
+	RETURN fullver;
+END
+$$
+LANGUAGE 'plpgsql' IMMUTABLE;
+
+---------------------------------------------------------------
+-- CASTS
+---------------------------------------------------------------
+
+-- Legacy ST_ variants of casts, to be removed in 2.0
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d(geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d(geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box(geometry)
+	RETURNS box
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d(box3d)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','BOX3D_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d(box2d)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box(box3d)
+	RETURNS box
+	AS '$libdir/postgis-1.5','BOX3D_to_BOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_text(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5','LWGEOM_to_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(box2d)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(box3d)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX3D_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','parse_WKT_lwgeom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(chip)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','CHIP_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_bytea'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_bytea(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_to_bytea'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d_extent(box3d_extent)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_extent_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d(box3d_extent)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX3D_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(box3d_extent)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX3D_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 	
+		
+CREATE OR REPLACE FUNCTION box2d(geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d(geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box(geometry)
+	RETURNS box
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box2d(box3d)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','BOX3D_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d(box2d)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box(box3d)
+	RETURNS box
+	AS '$libdir/postgis-1.5','BOX3D_to_BOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION text(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5','LWGEOM_to_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- this is kept for backward-compatibility
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION box3dtobox(box3d)
+	RETURNS box
+	AS 'SELECT box($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(box2d)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(box3d)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX3D_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','parse_WKT_lwgeom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(chip)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','CHIP_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_bytea'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION bytea(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_to_bytea'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- 7.3+ explicit casting definitions
+CREATE CAST (geometry AS box2d) WITH FUNCTION box2d(geometry) AS IMPLICIT;
+CREATE CAST (geometry AS box3d) WITH FUNCTION box3d(geometry) AS IMPLICIT;
+CREATE CAST (geometry AS box) WITH FUNCTION box(geometry) AS IMPLICIT;
+CREATE CAST (box3d AS box2d) WITH FUNCTION box2d(box3d) AS IMPLICIT;
+CREATE CAST (box2d AS box3d) WITH FUNCTION box3d(box2d) AS IMPLICIT;
+CREATE CAST (box2d AS geometry) WITH FUNCTION geometry(box2d) AS IMPLICIT;
+CREATE CAST (box3d AS box) WITH FUNCTION box(box3d) AS IMPLICIT;
+CREATE CAST (box3d AS geometry) WITH FUNCTION geometry(box3d) AS IMPLICIT;
+CREATE CAST (text AS geometry) WITH FUNCTION geometry(text) AS IMPLICIT;
+CREATE CAST (geometry AS text) WITH FUNCTION text(geometry) AS IMPLICIT;
+CREATE CAST (chip AS geometry) WITH FUNCTION geometry(chip) AS IMPLICIT;
+CREATE CAST (bytea AS geometry) WITH FUNCTION geometry(bytea) AS IMPLICIT;
+CREATE CAST (geometry AS bytea) WITH FUNCTION bytea(geometry) AS IMPLICIT;
+
+-- Casts to allow the box3d_extent type to automatically cast to box3d/box2d in queries
+CREATE CAST (box3d_extent AS box3d) WITH FUNCTION box3d_extent(box3d_extent) AS IMPLICIT;
+CREATE CAST (box3d_extent AS box2d) WITH FUNCTION box2d(box3d_extent) AS IMPLICIT;
+CREATE CAST (box3d_extent AS geometry) WITH FUNCTION geometry(box3d_extent) AS IMPLICIT;
+
+---------------------------------------------------------------
+-- Algorithms
+---------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Simplify(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_simplify2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Simplify(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_simplify2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- SnapToGrid(input, xoff, yoff, xsize, ysize)
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SnapToGrid(geometry, float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_snaptogrid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_snaptogrid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- SnapToGrid(input, xsize, ysize) # offsets=0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SnapToGrid(geometry, float8, float8)
+	RETURNS geometry
+	AS 'SELECT SnapToGrid($1, 0, 0, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, float8, float8)
+	RETURNS geometry
+	AS 'SELECT ST_SnapToGrid($1, 0, 0, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- SnapToGrid(input, size) # xsize=ysize=size, offsets=0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SnapToGrid(geometry, float8)
+	RETURNS geometry
+	AS 'SELECT SnapToGrid($1, 0, 0, $2, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, float8)
+	RETURNS geometry
+	AS 'SELECT ST_SnapToGrid($1, 0, 0, $2, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- SnapToGrid(input, point_offsets, xsize, ysize, zsize, msize)
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SnapToGrid(geometry, geometry, float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_snaptogrid_pointoff'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, geometry, float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_snaptogrid_pointoff'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Segmentize(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_segmentize2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Segmentize(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_segmentize2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+---------------------------------------------------------------
+-- LRS
+---------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION line_interpolate_point(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_interpolate_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_line_interpolate_point(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_interpolate_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION line_substring(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_substring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_line_substring(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_substring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION line_locate_point(geometry, geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_locate_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_line_locate_point(geometry, geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_locate_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION locate_between_measures(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_locate_between_m'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_locate_between_measures(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_locate_between_m'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION locate_along_measure(geometry, float8)
+	RETURNS geometry
+	AS $$ SELECT locate_between_measures($1, $2, $2) $$
+	LANGUAGE 'sql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_locate_along_measure(geometry, float8)
+	RETURNS geometry
+	AS $$ SELECT locate_between_measures($1, $2, $2) $$
+	LANGUAGE 'sql' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_AddMeasure(geometry, float8, float8) 
+	RETURNS geometry 
+	AS '$libdir/postgis-1.5', 'ST_AddMeasure' 
+	LANGUAGE 'C' IMMUTABLE STRICT;
+    
+---------------------------------------------------------------
+-- GEOS
+---------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION intersection(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','intersection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: intersection(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Intersection(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','intersection'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION buffer(geometry,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','buffer'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- PostGIS equivalent function: buffer(geometry,float8)
+CREATE OR REPLACE FUNCTION ST_Buffer(geometry,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','buffer'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.5.0 - requires GEOS-3.2 or higher
+CREATE OR REPLACE FUNCTION _ST_Buffer(geometry,float8,cstring)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','buffer'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Buffer(geometry,float8,integer)
+	RETURNS geometry
+	AS $$ SELECT _ST_Buffer($1, $2,
+		CAST('quad_segs='||CAST($3 AS text) as cstring))
+	   $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_buffer(geometry,float8,text)
+	RETURNS geometry
+	AS $$ SELECT _ST_Buffer($1, $2,
+		CAST( regexp_replace($3, '^[0123456789]+$',
+			'quad_segs='||$3) AS cstring)
+		)
+	   $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION buffer(geometry,float8,integer)
+	RETURNS geometry
+	AS 'SELECT ST_Buffer($1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION convexhull(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','convexhull'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- PostGIS equivalent function: convexhull(geometry)
+CREATE OR REPLACE FUNCTION ST_ConvexHull(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','convexhull'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Only accepts LINESTRING as parameters.
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION _ST_LineCrossingDirection(geometry, geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5', 'ST_LineCrossingDirection'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_LineCrossingDirection(geometry, geometry)
+	RETURNS integer AS
+	$$ SELECT CASE WHEN NOT $1 && $2 THEN 0 ELSE _ST_LineCrossingDirection($1,$2) END $$
+	LANGUAGE 'sql' IMMUTABLE;
+
+
+-- Only accepts LINESTRING as parameters.
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_LocateBetweenElevations(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'ST_LocateBetweenElevations'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Requires GEOS >= 3.0.0
+-- Availability: 1.3.3
+CREATE OR REPLACE FUNCTION ST_SimplifyPreserveTopology(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','topologypreservesimplify'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Requires GEOS >= 3.1.0
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_IsValidReason(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'isvalidreason'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+
+-- Requires GEOS >= 3.2.0
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_HausdorffDistance(geometry, geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'hausdorffdistance'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+
+
+-- Requires GEOS >= 3.2.0
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_HausdorffDistance(geometry, geometry, float8)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'hausdorffdistancedensify'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION difference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','difference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: difference(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Difference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','difference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION boundary(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','boundary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: boundary(geometry)
+CREATE OR REPLACE FUNCTION ST_Boundary(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','boundary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION symdifference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','symdifference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: symdifference(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_SymDifference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','symdifference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION symmetricdifference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','symdifference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_symmetricdifference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','symdifference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomUnion(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geomunion'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: GeomUnion(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Union(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geomunion'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+--------------------------------------------------------------------------------
+-- Aggregates and their supporting functions
+--------------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION collect(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_collect'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_collect(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_collect'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE memcollect(
+	sfunc = ST_collect,
+	basetype = geometry,
+	stype = geometry
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_memcollect(
+	sfunc = ST_collect,
+	basetype = geometry,
+	stype = geometry
+	);
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_collect (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_collect_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE MemGeomUnion (
+	basetype = geometry,
+	sfunc = geomunion,
+	stype = geometry
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_MemUnion (
+	basetype = geometry,
+	sfunc = ST_Union,
+	stype = geometry
+	);
+
+--
+-- pgis_abs
+-- Container type to hold the ArrayBuildState pointer as it passes through
+-- the geometry array accumulation aggregate.
+--
+CREATE OR REPLACE FUNCTION pgis_abs_in(cstring)
+	RETURNS pgis_abs
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION pgis_abs_out(pgis_abs)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE pgis_abs (
+	internallength = 8,
+	input = pgis_abs_in,
+	output = pgis_abs_out,
+	alignment = double
+);
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_accum_transfn(pgis_abs, geometry)
+	RETURNS pgis_abs
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_accum_finalfn(pgis_abs)
+	RETURNS geometry[]
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_union_finalfn(pgis_abs)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_collect_finalfn(pgis_abs)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_polygonize_finalfn(pgis_abs)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_makeline_finalfn(pgis_abs)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Deprecation in: 1.2.3
+CREATE AGGREGATE accum (
+	sfunc = pgis_geometry_accum_transfn,
+	basetype = geometry,
+	stype = pgis_abs,
+	finalfunc = pgis_geometry_accum_finalfn
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Accum (
+	sfunc = pgis_geometry_accum_transfn,
+	basetype = geometry,
+	stype = pgis_abs,
+	finalfunc = pgis_geometry_accum_finalfn
+	);
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION unite_garray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'pgis_union_geometry_array'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.4.0
+CREATE OR REPLACE FUNCTION ST_unite_garray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','pgis_union_geometry_array'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_Union (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','pgis_union_geometry_array'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Union (
+	basetype = geometry,
+	sfunc = pgis_geometry_accum_transfn,
+	stype = pgis_abs,
+	finalfunc = pgis_geometry_union_finalfn
+	);
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE collect (
+	basetype = geometry,
+	sfunc = pgis_geometry_accum_transfn,
+	stype = pgis_abs,
+	finalfunc = pgis_geometry_collect_finalfn
+);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Collect (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_collect_finalfn
+	);
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE Polygonize (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_polygonize_finalfn
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Polygonize (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_polygonize_finalfn
+	);
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE makeline (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_makeline_finalfn
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_MakeLine (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_makeline_finalfn
+	);
+
+
+
+--------------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION relate(geometry,geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5','relate_full'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_relate(geometry,geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5','relate_full'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION relate(geometry,geometry,text)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','relate_pattern'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: relate(geometry,geometry,text)
+CREATE OR REPLACE FUNCTION ST_Relate(geometry,geometry,text)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','relate_pattern'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION disjoint(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: disjoint(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Disjoint(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','disjoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION touches(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: touches(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Touches(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','touches'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Touches(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Touches($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.3.4
+CREATE OR REPLACE FUNCTION _ST_DWithin(geometry,geometry,float8)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_dwithin'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_DWithin(geometry, geometry, float8)
+	RETURNS boolean
+	AS 'SELECT $1 && ST_Expand($2,$3) AND $2 && ST_Expand($1,$3) AND _ST_DWithin($1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION intersects(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: intersects(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Intersects(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','intersects'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Intersects(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Intersects($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+	
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION crosses(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: crosses(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Crosses(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','crosses'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Crosses(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Crosses($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION within(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: within(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Within(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','within'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Within(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Within($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Contains(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: contains(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Contains(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','contains'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Contains(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Contains($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION _ST_CoveredBy(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'coveredby'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_CoveredBy(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_CoveredBy($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION _ST_Covers(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'covers'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Covers(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Covers($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION _ST_ContainsProperly(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','containsproperly'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.4.0
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_ContainsProperly(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_ContainsProperly($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION overlaps(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: overlaps(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Overlaps(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','overlaps'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Overlaps(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Overlaps($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsValid(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'isvalid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- PostGIS equivalent function: IsValid(geometry)
+-- TODO: change null returns to true
+CREATE OR REPLACE FUNCTION ST_IsValid(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'isvalid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- This is also available w/out GEOS
+CREATE OR REPLACE FUNCTION Centroid(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- PostGIS equivalent function: Centroid(geometry)
+CREATE OR REPLACE FUNCTION ST_Centroid(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'centroid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsRing(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: IsRing(geometry)
+CREATE OR REPLACE FUNCTION ST_IsRing(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'isring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointOnSurface(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PointOnSurface(geometry)
+CREATE OR REPLACE FUNCTION ST_PointOnSurface(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'pointonsurface'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsSimple(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'issimple'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: IsSimple(geometry)
+CREATE OR REPLACE FUNCTION ST_IsSimple(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'issimple'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Equals(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','geomequals'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_Equals(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','geomequals'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.1
+CREATE OR REPLACE FUNCTION ST_Equals(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Equals($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+
+-----------------------------------------------------------------------
+-- GML & KML INPUT
+-- Availability: 1.5.0
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION ST_GeomFromGML(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geom_from_gml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_GMLToSQL(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geom_from_gml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_GeomFromKML(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geom_from_kml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- SVG OUTPUT
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsSVG(geometry,int4,int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsSVG(geometry,int4,int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsSVG(geometry,int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsSVG(geometry,int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsSVG(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsSVG(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- GML OUTPUT
+-----------------------------------------------------------------------
+-- _ST_AsGML(version, geom, precision, option)
+CREATE OR REPLACE FUNCTION _ST_AsGML(int4, geometry, int4, int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asGML'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- AsGML(geom, precision) / version=2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsGML(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsGML(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- AsGML(geom) / precision=15 version=2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsGML(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsGML(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geom) / precision=15 version=2
+-- Availability: 1.3.2
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML($1, $2, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geom, precision)
+-- Availability: 1.3.2
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML (geom, precision, option) / version=2
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_AsGML(geometry, int4, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geom, precision, option)
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geometry, int4, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- KML OUTPUT
+-----------------------------------------------------------------------
+-- _ST_AsKML(version, geom, precision)
+CREATE OR REPLACE FUNCTION _ST_AsKML(int4, geometry, int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asKML'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- AsKML(geom, precision) / version=2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsKML(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML(2, transform($1,4326), $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsKML(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML(2, ST_Transform($1,4326), $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- AsKML(geom) / precision=15 version=2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsKML(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML(2, transform($1,4326), 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- AsKML(version, geom, precision)
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsKML(int4, geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML($1, transform($2,4326), $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsKML(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML(2, ST_Transform($1,4326), 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsKML(version, geom) / precision=15 version=2
+-- Availability: 1.3.2
+CREATE OR REPLACE FUNCTION ST_AsKML(int4, geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML($1, ST_Transform($2,4326), 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsKML(version, geom, precision)
+-- Availability: 1.3.2
+CREATE OR REPLACE FUNCTION ST_AsKML(int4, geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML($1, ST_Transform($2,4326), $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- GEOJSON OUTPUT
+-- Availability: 1.3.4
+-----------------------------------------------------------------------
+-- _ST_AsGeoJson(version, geom, precision, options)
+CREATE OR REPLACE FUNCTION _ST_AsGeoJson(int4, geometry, int4, int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asGeoJson'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geom, precision) / version=1 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson(1, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geom) / precision=15 version=1 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson(1, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geom) / precision=15 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson($1, $2, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geom, precision) / options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geom, precision, options) / version=1
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geometry, int4, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson(1, $1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geom, precision,options)
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geometry, int4, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- GeoHash (geohash.org)
+------------------------------------------------------------------------
+
+-- Availability 1.4.0
+CREATE OR REPLACE FUNCTION ST_GeoHash(geometry, int4)
+	RETURNS TEXT
+		AS '$libdir/postgis-1.5', 'ST_GeoHash'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability 1.4.0
+CREATE OR REPLACE FUNCTION ST_GeoHash(geometry)
+	RETURNS TEXT
+	AS 'SELECT ST_GeoHash($1, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- OGC defined
+------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION NumPoints(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_numpoints_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: NumPoints(geometry)
+CREATE OR REPLACE FUNCTION ST_NumPoints(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_numpoints_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION NumGeometries(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_numgeometries_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: NumGeometries(geometry)
+CREATE OR REPLACE FUNCTION ST_NumGeometries(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_numgeometries_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeometryN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_geometryn_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: GeometryN(geometry)
+CREATE OR REPLACE FUNCTION ST_GeometryN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_geometryn_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Dimension(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_dimension'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: Dimension(geometry)
+CREATE OR REPLACE FUNCTION ST_Dimension(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_dimension'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ExteriorRing(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_exteriorring_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: ExteriorRing(geometry)
+CREATE OR REPLACE FUNCTION ST_ExteriorRing(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_exteriorring_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION NumInteriorRings(geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','LWGEOM_numinteriorrings_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: NumInteriorRings(geometry)
+CREATE OR REPLACE FUNCTION ST_NumInteriorRings(geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','LWGEOM_numinteriorrings_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION NumInteriorRing(geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','LWGEOM_numinteriorrings_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_NumInteriorRing(geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','LWGEOM_numinteriorrings_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION InteriorRingN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_interiorringn_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: InteriorRingN(geometry)
+CREATE OR REPLACE FUNCTION ST_InteriorRingN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_interiorringn_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeometryType(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'LWGEOM_getTYPE'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Not quite equivalent to GeometryType
+CREATE OR REPLACE FUNCTION ST_GeometryType(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'geometry_geometrytype'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_pointn_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PointN(geometry,integer)
+CREATE OR REPLACE FUNCTION ST_PointN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_pointn_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION X(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_x_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: X(geometry)
+CREATE OR REPLACE FUNCTION ST_X(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_x_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Y(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_y_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: Y(geometry)
+CREATE OR REPLACE FUNCTION ST_Y(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_y_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Z(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_z_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Z(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_z_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION M(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_m_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_M(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_m_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION StartPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_startpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: StartPoint(geometry))
+CREATE OR REPLACE FUNCTION ST_StartPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_startpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION EndPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_endpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: EndPoint(geometry))
+CREATE OR REPLACE FUNCTION ST_EndPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_endpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsClosed(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_isclosed_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: IsClosed(geometry)
+CREATE OR REPLACE FUNCTION ST_IsClosed(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_isclosed_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsEmpty(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_isempty'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: IsEmpty(geometry)
+CREATE OR REPLACE FUNCTION ST_IsEmpty(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_isempty'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SRID(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','LWGEOM_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: getSRID(geometry)
+CREATE OR REPLACE FUNCTION ST_SRID(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','LWGEOM_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SetSRID(geometry,int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_setSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SetSRID(geometry,int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_setSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsBinary(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_asBinary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: AsBinary(geometry)
+CREATE OR REPLACE FUNCTION ST_AsBinary(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_asBinary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsBinary(geometry,text)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_asBinary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsBinary(geometry,text)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_asBinary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsText(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asText'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: AsText(geometry)
+CREATE OR REPLACE FUNCTION ST_AsText(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asText'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeometryFromText(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeometryFromText(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeometryFromText(text, int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeometryFromText(text, int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromText(text)
+	RETURNS geometry AS 'SELECT geometryfromtext($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomFromText(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromText(text, int4)
+	RETURNS geometry AS 'SELECT geometryfromtext($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: ST_GeometryFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_GeomFromText(text, int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''POINT''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PointFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''POINT''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''POINT''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PointFromText(text, int4)
+-- TODO: improve this ... by not duplicating constructor time.
+CREATE OR REPLACE FUNCTION ST_PointFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1, $2)) = ''POINT''
+	THEN ST_GeomFromText($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''LINESTRING''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LineFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''LINESTRING''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''LINESTRING''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: LineFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_LineFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''LINESTRING''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineStringFromText(text)
+	RETURNS geometry
+	AS 'SELECT LineFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineStringFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT LineFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolyFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''POLYGON''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolyFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''POLYGON''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolyFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''POLYGON''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: ST_PolygonFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_PolyFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1, $2)) = ''POLYGON''
+	THEN ST_GeomFromText($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolygonFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT PolyFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolygonFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT PolyFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolygonFromText(text)
+	RETURNS geometry
+	AS 'SELECT PolyFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolygonFromText(text)
+	RETURNS geometry
+	AS 'SELECT ST_PolyFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MLineFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromText($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MLineFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_MLineFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromText($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MLineFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''MULTILINESTRING''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MLineFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''MULTILINESTRING''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiLineStringFromText(text)
+	RETURNS geometry
+	AS 'SELECT ST_MLineFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiLineStringFromText(text)
+	RETURNS geometry
+	AS 'SELECT ST_MLineFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiLineStringFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MLineFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiLineStringFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MLineFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPointFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1,$2)) = ''MULTIPOINT''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MPointFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_MPointFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''MULTIPOINT''
+	THEN GeomFromText($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPointFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''MULTIPOINT''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MPointFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''MULTIPOINT''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MPointFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPointFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPointFromText(text)
+	RETURNS geometry
+	AS 'SELECT ST_MPointFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPointFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPointFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPointFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPolyFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''MULTIPOLYGON''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MPolyFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_MPolyFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1, $2)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPolyFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''MULTIPOLYGON''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+--Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MPolyFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPolygonFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MPolyFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPolygonFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MPolyFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPolygonFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPolyFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPolygonFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPolyFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomCollFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromText($1, $2)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomCollFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(ST_GeomFromText($1, $2)) = ''GEOMETRYCOLLECTION''
+	THEN ST_GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomCollFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromText($1)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomCollFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(ST_GeomFromText($1)) = ''GEOMETRYCOLLECTION''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromWKB(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_WKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomFromWKB(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_WKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromWKB(bytea, int)
+	RETURNS geometry
+	AS 'SELECT setSRID(GeomFromWKB($1), $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: GeomFromWKB(bytea, int)
+CREATE OR REPLACE FUNCTION ST_GeomFromWKB(bytea, int)
+	RETURNS geometry
+	AS 'SELECT ST_SetSRID(ST_GeomFromWKB($1), $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''POINT''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PointFromWKB(bytea, int)
+CREATE OR REPLACE FUNCTION ST_PointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''POINT''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''POINT''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''POINT''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''LINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: LineFromWKB(bytea, int)
+CREATE OR REPLACE FUNCTION ST_LineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''LINESTRING''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''LINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''LINESTRING''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LinestringFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''LINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LinestringFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''LINESTRING''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LinestringFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''LINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LinestringFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''LINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''POLYGON''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PolyFromWKB(text, int)
+CREATE OR REPLACE FUNCTION ST_PolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''POLYGON''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''POLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''POLYGON''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolygonFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1,$2)) = ''POLYGON''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolygonFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1,$2)) = ''POLYGON''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolygonFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''POLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolygonFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''POLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1,$2)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MPointFromWKB(text, int)
+CREATE OR REPLACE FUNCTION ST_MPointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MPointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTIPOINT''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1,$2)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1,$2)) = ''MULTIPOINT''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTIPOINT''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiLineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION MultiLineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiLineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiLineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTILINESTRING''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MLineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MLineFromWKB(text, int)
+CREATE OR REPLACE FUNCTION ST_MLineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''MULTILINESTRING''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MLineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MLineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTILINESTRING''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTIPOLYGON''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MPolyFromWKB(bytea, int)
+CREATE OR REPLACE FUNCTION ST_MPolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTIPOLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MPolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTIPOLYGON''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTIPOLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomCollFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromWKB($1, $2)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomCollFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromWKB($1, $2)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomCollFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromWKB($1)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomCollFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(ST_GeomFromWKB($1)) = ''GEOMETRYCOLLECTION''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+--New functions
+
+-- Maximum distance between linestrings.
+
+CREATE OR REPLACE FUNCTION max_distance(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_maxdistance2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_MaxDistance(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_maxdistance2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+	
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_MaxDistance(geometry,geometry)
+	RETURNS float8
+	AS 'SELECT _ST_MaxDistance(ST_ConvexHull($1), ST_ConvexHull($2))'
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+CREATE OR REPLACE FUNCTION ST_ClosestPoint(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_closestpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_ShortestLine(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_shortestline2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION _ST_LongestLine(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_longestline2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_LongestLine(geometry,geometry)
+	RETURNS geometry
+	AS 'SELECT _ST_LongestLine(ST_ConvexHull($1), ST_ConvexHull($2))'
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+CREATE OR REPLACE FUNCTION _ST_DFullyWithin(geometry,geometry,float8)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_dfullywithin'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+CREATE OR REPLACE FUNCTION ST_DFullyWithin(geometry, geometry, float8)
+	RETURNS boolean
+	AS 'SELECT $1 && ST_Expand($2,$3) AND $2 && ST_Expand($1,$3) AND _ST_DFullyWithin(ST_ConvexHull($1), ST_ConvexHull($2), $3)'
+	LANGUAGE 'SQL' IMMUTABLE; 
+	
+	
+--
+-- SFSQL 1.1
+--
+-- BdPolyFromText(multiLineStringTaggedText String, SRID Integer): Polygon
+--
+--  Construct a Polygon given an arbitrary
+--  collection of closed linestrings as a
+--  MultiLineString text representation.
+--
+-- This is a PLPGSQL function rather then an SQL function
+-- To avoid double call of BuildArea (one to get GeometryType
+-- and another to actual return, in a CASE WHEN construct).
+-- Also, we profit from plpgsql to RAISE exceptions.
+--
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION BdPolyFromText(text, integer)
+RETURNS geometry
+AS $$
+DECLARE
+	geomtext alias for $1;
+	srid alias for $2;
+	mline geometry;
+	geom geometry;
+BEGIN
+	mline := MultiLineStringFromText(geomtext, srid);
+
+	IF mline IS NULL
+	THEN
+		RAISE EXCEPTION 'Input is not a MultiLinestring';
+	END IF;
+
+	geom := BuildArea(mline);
+
+	IF GeometryType(geom) != 'POLYGON'
+	THEN
+		RAISE EXCEPTION 'Input returns more then a single polygon, try using BdMPolyFromText instead';
+	END IF;
+
+	RETURN geom;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_BdPolyFromText(text, integer)
+RETURNS geometry
+AS $$
+DECLARE
+	geomtext alias for $1;
+	srid alias for $2;
+	mline geometry;
+	geom geometry;
+BEGIN
+	mline := ST_MultiLineStringFromText(geomtext, srid);
+
+	IF mline IS NULL
+	THEN
+		RAISE EXCEPTION 'Input is not a MultiLinestring';
+	END IF;
+
+	geom := ST_BuildArea(mline);
+
+	IF GeometryType(geom) != 'POLYGON'
+	THEN
+		RAISE EXCEPTION 'Input returns more then a single polygon, try using BdMPolyFromText instead';
+	END IF;
+
+	RETURN geom;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+--
+-- SFSQL 1.1
+--
+-- BdMPolyFromText(multiLineStringTaggedText String, SRID Integer): MultiPolygon
+--
+--  Construct a MultiPolygon given an arbitrary
+--  collection of closed linestrings as a
+--  MultiLineString text representation.
+--
+-- This is a PLPGSQL function rather then an SQL function
+-- To raise an exception in case of invalid input.
+--
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION BdMPolyFromText(text, integer)
+RETURNS geometry
+AS $$
+DECLARE
+	geomtext alias for $1;
+	srid alias for $2;
+	mline geometry;
+	geom geometry;
+BEGIN
+	mline := MultiLineStringFromText(geomtext, srid);
+
+	IF mline IS NULL
+	THEN
+		RAISE EXCEPTION 'Input is not a MultiLinestring';
+	END IF;
+
+	geom := multi(BuildArea(mline));
+
+	RETURN geom;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_BdMPolyFromText(text, integer)
+RETURNS geometry
+AS $$
+DECLARE
+	geomtext alias for $1;
+	srid alias for $2;
+	mline geometry;
+	geom geometry;
+BEGIN
+	mline := ST_MultiLineStringFromText(geomtext, srid);
+
+	IF mline IS NULL
+	THEN
+		RAISE EXCEPTION 'Input is not a MultiLinestring';
+	END IF;
+
+	geom := multi(ST_BuildArea(mline));
+
+	RETURN geom;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-- 
+-- $Id: long_xact.sql.in.c 4894 2009-11-25 19:15:57Z pramsey $
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- http://postgis.refractions.net
+-- Copyright 2001-2003 Refractions Research Inc.
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--  
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+
+
+
+-----------------------------------------------------------------------
+-- LONG TERM LOCKING
+-----------------------------------------------------------------------
+
+-- UnlockRows(authid)
+-- removes all locks held by the given auth
+-- returns the number of locks released
+CREATE OR REPLACE FUNCTION UnlockRows(text)
+	RETURNS int
+	AS $$ 
+DECLARE
+	ret int;
+BEGIN
+
+	IF NOT LongTransactionsEnabled() THEN
+		RAISE EXCEPTION 'Long transaction support disabled, use EnableLongTransaction() to enable.';
+	END IF;
+
+	EXECUTE 'DELETE FROM authorization_table where authid = ' ||
+		quote_literal($1);
+
+	GET DIAGNOSTICS ret = ROW_COUNT;
+
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql'  VOLATILE STRICT;
+
+-- LockRow([schema], table, rowid, auth, [expires]) 
+-- Returns 1 if successfully obtained the lock, 0 otherwise
+CREATE OR REPLACE FUNCTION LockRow(text, text, text, text, timestamp)
+	RETURNS int
+	AS $$ 
+DECLARE
+	myschema alias for $1;
+	mytable alias for $2;
+	myrid   alias for $3;
+	authid alias for $4;
+	expires alias for $5;
+	ret int;
+	mytoid oid;
+	myrec RECORD;
+	
+BEGIN
+
+	IF NOT LongTransactionsEnabled() THEN
+		RAISE EXCEPTION 'Long transaction support disabled, use EnableLongTransaction() to enable.';
+	END IF;
+
+	EXECUTE 'DELETE FROM authorization_table WHERE expires < now()'; 
+
+	SELECT c.oid INTO mytoid FROM pg_class c, pg_namespace n
+		WHERE c.relname = mytable
+		AND c.relnamespace = n.oid
+		AND n.nspname = myschema;
+
+	-- RAISE NOTICE 'toid: %', mytoid;
+
+	FOR myrec IN SELECT * FROM authorization_table WHERE 
+		toid = mytoid AND rid = myrid
+	LOOP
+		IF myrec.authid != authid THEN
+			RETURN 0;
+		ELSE
+			RETURN 1;
+		END IF;
+	END LOOP;
+
+	EXECUTE 'INSERT INTO authorization_table VALUES ('||
+		quote_literal(mytoid::text)||','||quote_literal(myrid)||
+		','||quote_literal(expires::text)||
+		','||quote_literal(authid) ||')';
+
+	GET DIAGNOSTICS ret = ROW_COUNT;
+
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql'  VOLATILE STRICT;
+
+-- LockRow(schema, table, rid, authid);
+CREATE OR REPLACE FUNCTION LockRow(text, text, text, text)
+	RETURNS int
+	AS
+$$ SELECT LockRow($1, $2, $3, $4, now()::timestamp+'1:00'); $$
+	LANGUAGE 'sql'  VOLATILE STRICT;
+
+-- LockRow(table, rid, authid);
+CREATE OR REPLACE FUNCTION LockRow(text, text, text)
+	RETURNS int
+	AS
+$$ SELECT LockRow(current_schema(), $1, $2, $3, now()::timestamp+'1:00'); $$
+	LANGUAGE 'sql'  VOLATILE STRICT;
+
+-- LockRow(schema, table, rid, expires);
+CREATE OR REPLACE FUNCTION LockRow(text, text, text, timestamp)
+	RETURNS int
+	AS
+$$ SELECT LockRow(current_schema(), $1, $2, $3, $4); $$
+	LANGUAGE 'sql'  VOLATILE STRICT;
+
+
+CREATE OR REPLACE FUNCTION AddAuth(text)
+	RETURNS BOOLEAN
+	AS $$ 
+DECLARE
+	lockid alias for $1;
+	okay boolean;
+	myrec record;
+BEGIN
+	-- check to see if table exists
+	--  if not, CREATE TEMP TABLE mylock (transid xid, lockcode text)
+	okay := 'f';
+	FOR myrec IN SELECT * FROM pg_class WHERE relname = 'temp_lock_have_table' LOOP
+		okay := 't';
+	END LOOP; 
+	IF (okay <> 't') THEN 
+		CREATE TEMP TABLE temp_lock_have_table (transid xid, lockcode text);
+			-- this will only work from pgsql7.4 up
+			-- ON COMMIT DELETE ROWS;
+	END IF;
+
+	--  INSERT INTO mylock VALUES ( $1)
+--	EXECUTE 'INSERT INTO temp_lock_have_table VALUES ( '||
+--		quote_literal(getTransactionID()) || ',' ||
+--		quote_literal(lockid) ||')';
+
+	INSERT INTO temp_lock_have_table VALUES (getTransactionID(), lockid);
+
+	RETURN true::boolean;
+END;
+$$
+LANGUAGE PLPGSQL;
+ 
+
+-- CheckAuth( <schema>, <table>, <ridcolumn> )
+--
+-- Returns 0
+--
+CREATE OR REPLACE FUNCTION CheckAuth(text, text, text)
+	RETURNS INT
+	AS $$ 
+DECLARE
+	schema text;
+BEGIN
+	IF NOT LongTransactionsEnabled() THEN
+		RAISE EXCEPTION 'Long transaction support disabled, use EnableLongTransaction() to enable.';
+	END IF;
+
+	if ( $1 != '' ) THEN
+		schema = $1;
+	ELSE
+		SELECT current_schema() into schema;
+	END IF;
+
+	-- TODO: check for an already existing trigger ?
+
+	EXECUTE 'CREATE TRIGGER check_auth BEFORE UPDATE OR DELETE ON ' 
+		|| quote_ident(schema) || '.' || quote_ident($2)
+		||' FOR EACH ROW EXECUTE PROCEDURE CheckAuthTrigger('
+		|| quote_literal($3) || ')';
+
+	RETURN 0;
+END;
+$$
+LANGUAGE 'plpgsql';
+
+-- CheckAuth(<table>, <ridcolumn>)
+CREATE OR REPLACE FUNCTION CheckAuth(text, text)
+	RETURNS INT
+	AS
+	$$ SELECT CheckAuth('', $1, $2) $$
+	LANGUAGE 'SQL';
+
+CREATE OR REPLACE FUNCTION CheckAuthTrigger()
+	RETURNS trigger AS 
+	'$libdir/postgis-1.5', 'check_authorization'
+	LANGUAGE C;
+
+CREATE OR REPLACE FUNCTION GetTransactionID()
+	RETURNS xid AS 
+	'$libdir/postgis-1.5', 'getTransactionID'
+	LANGUAGE C;
+
+
+--
+-- Enable Long transactions support
+--
+--  Creates the authorization_table if not already existing
+--
+CREATE OR REPLACE FUNCTION EnableLongTransactions()
+	RETURNS TEXT
+	AS $$ 
+DECLARE
+	"query" text;
+	exists bool;
+	rec RECORD;
+
+BEGIN
+
+	exists = 'f';
+	FOR rec IN SELECT * FROM pg_class WHERE relname = 'authorization_table'
+	LOOP
+		exists = 't';
+	END LOOP;
+
+	IF NOT exists
+	THEN
+		"query" = 'CREATE TABLE authorization_table (
+			toid oid, -- table oid
+			rid text, -- row id
+			expires timestamp,
+			authid text
+		)';
+		EXECUTE "query";
+	END IF;
+
+	exists = 'f';
+	FOR rec IN SELECT * FROM pg_class WHERE relname = 'authorized_tables'
+	LOOP
+		exists = 't';
+	END LOOP;
+
+	IF NOT exists THEN
+		"query" = 'CREATE VIEW authorized_tables AS ' ||
+			'SELECT ' ||
+			'n.nspname as schema, ' ||
+			'c.relname as table, trim(' ||
+			quote_literal(chr(92) || '000') ||
+			' from t.tgargs) as id_column ' ||
+			'FROM pg_trigger t, pg_class c, pg_proc p ' ||
+			', pg_namespace n ' ||
+			'WHERE p.proname = ' || quote_literal('checkauthtrigger') ||
+			' AND c.relnamespace = n.oid' ||
+			' AND t.tgfoid = p.oid and t.tgrelid = c.oid';
+		EXECUTE "query";
+	END IF;
+
+	RETURN 'Long transactions support enabled';
+END;
+$$
+LANGUAGE 'plpgsql';
+
+--
+-- Check if Long transactions support is enabled
+--
+CREATE OR REPLACE FUNCTION LongTransactionsEnabled()
+	RETURNS bool
+AS $$ 
+DECLARE
+	rec RECORD;
+BEGIN
+	FOR rec IN SELECT oid FROM pg_class WHERE relname = 'authorized_tables'
+	LOOP
+		return 't';
+	END LOOP;
+	return 'f';
+END;
+$$
+LANGUAGE 'plpgsql';
+
+--
+-- Disable Long transactions support
+--
+--  (1) Drop any long_xact trigger 
+--  (2) Drop the authorization_table
+--  (3) KEEP the authorized_tables view
+--
+CREATE OR REPLACE FUNCTION DisableLongTransactions()
+	RETURNS TEXT
+	AS $$ 
+DECLARE
+	rec RECORD;
+
+BEGIN
+
+	--
+	-- Drop all triggers applied by CheckAuth()
+	--
+	FOR rec IN
+		SELECT c.relname, t.tgname, t.tgargs FROM pg_trigger t, pg_class c, pg_proc p
+		WHERE p.proname = 'checkauthtrigger' and t.tgfoid = p.oid and t.tgrelid = c.oid
+	LOOP
+		EXECUTE 'DROP TRIGGER ' || quote_ident(rec.tgname) ||
+			' ON ' || quote_ident(rec.relname);
+	END LOOP;
+
+	--
+	-- Drop the authorization_table table
+	--
+	FOR rec IN SELECT * FROM pg_class WHERE relname = 'authorization_table' LOOP
+		DROP TABLE authorization_table;
+	END LOOP;
+
+	--
+	-- Drop the authorized_tables view
+	--
+	FOR rec IN SELECT * FROM pg_class WHERE relname = 'authorized_tables' LOOP
+		DROP VIEW authorized_tables;
+	END LOOP;
+
+	RETURN 'Long transactions support disabled';
+END;
+$$
+LANGUAGE 'plpgsql';
+
+---------------------------------------------------------------
+-- END
+---------------------------------------------------------------
+
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-- 
+-- $Id: sqlmm.sql.in.c 4894 2009-11-25 19:15:57Z pramsey $
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- http://postgis.refractions.net
+-- Copyright 2001-2003 Refractions Research Inc.
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--  
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-- This file defines a subset of SQL/MM functions (that is, only those
+-- currently defined by ESRI's ArcSDE). Since these functions already exist
+-- in PostGIS (for the most part), these functions simply expose the current
+-- functions. Although mostly complying with SQL/MM standards, these prototypes
+-- follow ESRI's ArcSDE SQL Specifications and not SQL/MM standards where 
+-- disparities exist.
+--
+-- Specification Disparity Notes:
+--   * ST_OrderingEquals(geometry, geometry) is implemented as per
+--     ESRI's ArcSDE SQL specifications, not SQL/MM specifications.
+--     (http://edndoc.esri.com/arcsde/9.1/sql_api/sqlapi3.htm#ST_OrderingEquals)
+--   * Geometry constructors default to an SRID of -1, not 0 as per SQL/MM specs.
+--   * Boolean return type methods (ie. ST_IsValid, ST_IsEmpty, ...)
+--      * SQL/MM           : RETURNS 1 if TRUE, 0 if (FALSE, NULL)
+--      * ESRI in Informix : RETURNS 1 if (TRUE, NULL), 0 if FALSE
+--      * ESRI in DB2      : RETURNS 1 if TRUE, 0 if FALSE, NULL if NULL 
+--      * PostGIS          : RETURNS 1 if TRUE, 0 if FALSE, NULL if NULL 
+--
+-- TODO: Implement ESRI's Shape constructors
+--   * SE_AsShape(geometry)
+--   * SE_ShapeToSQL
+--   * SE_GeomFromShape
+--   * SE_PointFromShape
+--   * SE_LineFromShape
+--   * SE_PolyFromShape
+--   * SE_MPointFromShape
+--   * SE_MLineFromShape
+--   * SE_MPolyFromShape
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for constructing an ST_Geometry
+--     value given its WTK representation
+-- (http://edndoc.esri.com/arcsde/9.1/general_topics/storing_geo_in_rdbms.html)
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: ST_GeometryFromText(text)
+-- Note: Defaults to an SRID=-1, not 0 as per SQL/MM specs.
+CREATE OR REPLACE FUNCTION ST_WKTToSQL(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- ST_GeomFromText(text, int4) - already defined
+-- ST_PointFromText(text, int4) - already defined
+-- ST_LineFromText(text, int4) - already defined
+-- ST_PolyFromText(text, int4) - already defined
+-- ST_MPointFromText(text, int4) - already defined
+-- ST_MLineFromText(text, int4) - already defined
+-- ST_MPolyFromText(text, int4) - already defined
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for constructing an ST_Geometry
+--     value given its WKB representation
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: GeomFromWKB(bytea))
+-- Note: Defaults to an SRID=-1, not 0 as per SQL/MM specs.
+
+CREATE OR REPLACE FUNCTION ST_WKBToSQL(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_WKB'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- ST_GeomFromWKB(bytea, int) - already defined
+-- ST_PointFromWKB(bytea, int) - already defined
+-- ST_LineFromWKB(bytea, int) - already defined
+-- ST_PolyFromWKB(bytea, int) - already defined
+-- ST_MPointFromWKB(bytea, int) - already defined
+-- ST_MLineFromWKB(bytea, int) - already defined
+-- ST_MPolyFromWKB(bytea, int) - already defined
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for constructing an ST_Geometry
+--     value given an ESRI Shape representation
+-------------------------------------------------------------------------------
+
+-- TODO: SE_ShapeToSQL
+-- TODO: SE_GeomFromShape
+-- TODO: SE_PointFromShape
+-- TODO: SE_LineFromShape
+-- TODO: SE_PolyFromShape
+-- TODO: SE_MPointFromShape
+-- TODO: SE_MLineFromShape
+-- TODO: SE_MPolyFromShape
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for obtaining the WKT representation
+--     of an ST_Geometry
+-------------------------------------------------------------------------------
+
+-- ST_AsText(geometry) - already defined
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for obtaining the WKB representation
+--     of an ST_Geometry
+-------------------------------------------------------------------------------
+
+-- ST_AsBinary(geometry) - already defined
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for obtaining the ESRI Shape 
+-- representation of an ST_Geometry
+-------------------------------------------------------------------------------
+
+-- TODO: SE_AsShape(geometry)
+--CREATE OR REPLACE FUNCTION SE_AsShape(geometry)
+--    RETURNS bytea
+--    AS '$libdir/postgis-1.5','LWGEOM_AsShape'
+--    LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Geometry
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: ndims(geometry)
+CREATE OR REPLACE FUNCTION ST_CoordDim(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_ndims'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- ST_Dimension(geometry) - already defined.
+-- ST_GeometryType(geometry) - already defined.
+-- ST_SRID(geometry) - already defined.
+-- ST_IsEmpty(geometry) - already defined.
+-- ST_IsSimple(geometry) - already defined.
+-- ST_IsValid(geometry) - already defined.
+-- ST_Boundary(geometry) - already defined.
+-- ST_Envelope(geometry) - already defined.
+-- ST_Transform(geometry) - already defined.
+-- ST_AsText(geometry) - already defined.
+-- ST_AsBinary(geometry) - already defined.
+-- SE_AsShape(geometry) - already defined.
+-- ST_X(geometry) - already defined.
+-- ST_Y(geometry) - already defined.
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_OrderingEquals(geometry, geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_same'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.3.0
+CREATE OR REPLACE FUNCTION ST_OrderingEquals(geometry, geometry)
+	RETURNS boolean
+	AS $$ 
+	SELECT $1 ~= $2 AND _ST_OrderingEquals($1, $2)
+	$$	
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION SE_Is3D(geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_hasz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION SE_IsMeasured(geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_hasm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Point
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: makePoint(float8,float8)
+CREATE OR REPLACE FUNCTION ST_Point(float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- PostGIS equivalent function: Z(geometry)
+CREATE OR REPLACE FUNCTION SE_Z(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_z_point'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- PostGIS equivalent function: M(geometry)
+CREATE OR REPLACE FUNCTION SE_M(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_m_point'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Curve
+-------------------------------------------------------------------------------
+
+-- ST_StartPoint(geometry) - already defined.
+-- ST_EndPoint(geometry) - already defined.
+-- ST_IsClosed(geometry) - already defined.
+-- ST_IsRing(geometry) - already defined.
+-- ST_Length(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_LineString
+-------------------------------------------------------------------------------
+
+-- ST_NumPoints(geometry) - already defined.
+-- ST_PointN(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Surface
+-------------------------------------------------------------------------------
+
+-- ST_Centroid(geometry) - already defined.
+-- ST_PointOnSurface(geometry) - already defined.
+-- ST_Area(geometry) - already defined.
+-- ST_Perimeter(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Polygon
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: MakePolygon(geometry)
+CREATE OR REPLACE FUNCTION ST_Polygon(geometry, int)
+	RETURNS geometry
+	AS $$ 
+	SELECT setSRID(makepolygon($1), $2)
+	$$	
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+-- ST_ExteriorRing(geometry) - already defined.
+-- ST_NumInteriorRing(geometry) - already defined.
+-- ST_InteriorRingN(geometry, integer) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_GeomCollection
+-------------------------------------------------------------------------------
+
+-- ST_NumGeometries(geometry) - already defined.
+-- ST_GeometryN(geometry, integer) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_MultiCurve
+-------------------------------------------------------------------------------
+
+-- ST_IsClosed(geometry) - already defined.
+-- ST_Length(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_MultiSurface
+-------------------------------------------------------------------------------
+
+-- ST_Centroid(geometry) - already defined.
+-- ST_PointOnSurface(geometry) - already defined.
+-- ST_Area(geometry) - already defined.
+-- ST_Perimeter(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions that test spatial relationships
+-------------------------------------------------------------------------------
+
+-- ST_Equals(geometry, geometry) - already defined.
+-- ST_Disjoint(geometry, geometry) - already defined.
+-- ST_Touches(geometry, geometry) - already defined.
+-- ST_Within(geometry, geometry) - already defined.
+-- ST_Overlaps(geometry, geometry) - already defined.
+-- ST_Crosses(geometry, geometry) - already defined.
+-- ST_Intersects(geometry, geometry) - already defined.
+-- ST_Contains(geometry, geometry) - already defined.
+-- ST_Relate(geometry, geometry, text) - already defined.
+
+-- PostGIS equivalent function: none
+CREATE OR REPLACE FUNCTION SE_EnvelopesIntersect(geometry,geometry)
+	RETURNS boolean
+	AS $$ 
+	SELECT $1 && $2
+	$$	
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for distance relationships
+-------------------------------------------------------------------------------
+
+-- ST_Distance(geometry, geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions that implement spatial operators
+-------------------------------------------------------------------------------
+
+-- ST_Intersection(geometry, geometry) - already defined.
+-- ST_Difference(geometry, geometry) - already defined.
+-- ST_Union(geometry, geometry) - already defined.
+-- ST_SymDifference(geometry, geometry) - already defined.
+-- ST_Buffer(geometry, float8) - already defined.
+-- ST_ConvexHull(geometry) already defined.
+
+-- PostGIS equivalent function: locate_along_measure(geometry, float8)
+CREATE OR REPLACE FUNCTION SE_LocateAlong(geometry, float8)
+	RETURNS geometry
+	AS $$ SELECT locate_between_measures($1, $2, $2) $$
+	LANGUAGE 'sql' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: locate_between_measures(geometry, float8, float8)
+CREATE OR REPLACE FUNCTION SE_LocateBetween(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_locate_between_m'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+
+
+-------------------------------------------------------------------------------
+-- END
+-------------------------------------------------------------------------------
+
+
+---------------------------------------------------------------------------
+-- $Id: geography.sql.in.c 5065 2009-12-30 01:25:17Z pramsey $
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- Copyright 2009 Paul Ramsey <pramsey@cleverelephant.ca>
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--
+---------------------------------------------------------------------------
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_in(cstring[])
+	RETURNS integer
+	AS '$libdir/postgis-1.5','geography_typmod_in'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_out(integer)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','geography_typmod_out'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+	
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_in(cstring, oid, integer)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_in'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_out(geography)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','geography_out'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_analyze(internal)
+	RETURNS bool
+	AS '$libdir/postgis-1.5','geography_analyze'
+	LANGUAGE 'C' VOLATILE STRICT; 
+
+-- Availability: 1.5.0
+CREATE TYPE geography (
+	internallength = variable,
+	input = geography_in,
+	output = geography_out,
+	typmod_in = geography_typmod_in,
+	typmod_out = geography_typmod_out,
+	analyze = geography_analyze,
+	storage = main,
+	alignment = double
+);
+
+--
+-- GIDX type is used by the GiST index bindings. 
+-- In/out functions are stubs, as all access should be internal.
+---
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION gidx_in(cstring)
+	RETURNS gidx
+	AS '$libdir/postgis-1.5','gidx_in'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION gidx_out(gidx)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','gidx_out'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE TYPE gidx (
+	internallength = variable,
+	input = gidx_in,
+	output = gidx_out,
+	storage = plain,
+	alignment = double
+);
+
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography(geography, integer, boolean)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_enforce_typmod'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE CAST (geography AS geography) WITH FUNCTION geography(geography, integer, boolean) AS IMPLICIT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_AsText(geography)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsText(text)
+	RETURNS text AS
+	$$ SELECT ST_AsText($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_GeographyFromText(text)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_GeogFromText(text)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_AsBinary(geography)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','geography_as_binary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsBinary(text)
+	RETURNS bytea AS
+	$$ SELECT ST_AsBinary($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_GeogFromWKB(bytea)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_from_binary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_dims(integer)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','geography_typmod_dims'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_srid(integer)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','geography_typmod_srid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_type(integer)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_typmod_type'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE VIEW geography_columns AS
+	SELECT
+		current_database() AS f_table_catalog, 
+		n.nspname AS f_table_schema, 
+		c.relname AS f_table_name, 
+		a.attname AS f_geography_column,
+		geography_typmod_dims(a.atttypmod) AS coord_dimension,
+		geography_typmod_srid(a.atttypmod) AS srid,
+		geography_typmod_type(a.atttypmod) AS type
+	FROM 
+		pg_class c, 
+		pg_attribute a, 
+		pg_type t, 
+		pg_namespace n
+	WHERE c.relkind IN('r','v')
+	AND t.typname = 'geography'
+	AND a.attisdropped = false
+	AND a.atttypid = t.oid
+	AND a.attrelid = c.oid
+	AND c.relnamespace = n.oid;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography(geometry)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_from_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE CAST (geometry AS geography) WITH FUNCTION geography(geometry) AS IMPLICIT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geometry(geography)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geometry_from_geography'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE CAST (geography AS geometry) WITH FUNCTION geometry(geography) ;
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+-- GiST Support Functions
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_consistent(internal,geometry,int4) 
+	RETURNS bool 
+	AS '$libdir/postgis-1.5' ,'geography_gist_consistent'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_compress(internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5','geography_gist_compress'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_penalty(internal,internal,internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_penalty'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_picksplit(internal, internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_picksplit'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_union(bytea, internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_union'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_same(box2d, box2d, internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_same'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_decompress(internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_decompress'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_selectivity (internal, oid, internal, int4)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'geography_gist_selectivity'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_join_selectivity(internal, oid, internal, smallint)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'geography_gist_join_selectivity'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_overlaps(geography, geography) 
+	RETURNS boolean 
+	AS '$libdir/postgis-1.5' ,'geography_overlaps'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OPERATOR && (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_overlaps,
+	COMMUTATOR = '&&',
+	RESTRICT = geography_gist_selectivity, 
+	JOIN = geography_gist_join_selectivity
+);
+
+
+-- Availability: 1.5.0
+CREATE OPERATOR CLASS gist_geography_ops
+	DEFAULT FOR TYPE geography USING GIST AS
+	STORAGE 	gidx,
+	OPERATOR        3        &&	,
+--	OPERATOR        6        ~=	,
+--	OPERATOR        7        ~	,
+--	OPERATOR        8        @	,
+	FUNCTION        1        geography_gist_consistent (internal, geometry, int4),
+	FUNCTION        2        geography_gist_union (bytea, internal),
+	FUNCTION        3        geography_gist_compress (internal),
+	FUNCTION        4        geography_gist_decompress (internal),
+	FUNCTION        5        geography_gist_penalty (internal, internal, internal),
+	FUNCTION        6        geography_gist_picksplit (internal, internal),
+	FUNCTION        7        geography_gist_same (box2d, box2d, internal);
+
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+-- B-Tree Functions
+-- For sorting and grouping
+-- Availability: 1.5.0
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+CREATE OR REPLACE FUNCTION geography_lt(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_lt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_le(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_le'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_gt(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_gt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_ge(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_ge'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_eq(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_eq'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_cmp(geography, geography)
+	RETURNS integer
+	AS '$libdir/postgis-1.5', 'geography_cmp'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+--
+-- Sorting operators for Btree
+--
+
+CREATE OPERATOR < (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_lt,
+	COMMUTATOR = '>', NEGATOR = '>=',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR <= (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_le,
+	COMMUTATOR = '>=', NEGATOR = '>',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR = (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_eq,
+	COMMUTATOR = '=', -- we might implement a faster negator here
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR >= (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_ge,
+	COMMUTATOR = '<=', NEGATOR = '<',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+CREATE OPERATOR > (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_gt,
+	COMMUTATOR = '<', NEGATOR = '<=',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR CLASS btree_geography_ops
+	DEFAULT FOR TYPE geography USING btree AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	geography_cmp (geography, geography);
+
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+-- Export Functions
+-- Availability: 1.5.0
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+--
+-- SVG OUTPUT
+--
+
+-- ST_AsSVG(geography, precision, rel)
+CREATE OR REPLACE FUNCTION ST_AsSVG(geography,int4,int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_svg'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsSVG(geography, precision) / rel=0
+CREATE OR REPLACE FUNCTION ST_AsSVG(geography,int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_svg'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsSVG(geography) / precision=15, rel=0
+CREATE OR REPLACE FUNCTION ST_AsSVG(geography)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_svg'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsSVG(text)
+	RETURNS text AS
+	$$ SELECT ST_AsSVG($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+
+--
+-- GML OUTPUT
+--
+
+-- _ST_AsGML(version, geography, precision, option)
+CREATE OR REPLACE FUNCTION _ST_AsGML(int4, geography, int4, int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_gml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsGML(geography, precision) / version=2 options=0
+CREATE OR REPLACE FUNCTION ST_AsGML(geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGML(2, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(geography) / precision=15 version=2 options=0
+CREATE OR REPLACE FUNCTION ST_AsGML(geography)
+	RETURNS text
+	AS 'SELECT _ST_AsGML(2, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsGML(text)
+	RETURNS text AS
+	$$ SELECT ST_AsGML($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geography) / precision=15 version=2 options=0
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geography)
+	RETURNS text
+	AS 'SELECT _ST_AsGML($1, $2, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geography, precision) / options = 0
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGML($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML (geography, precision, option) / version=2
+CREATE OR REPLACE FUNCTION ST_AsGML(geography, int4, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGML(2, $1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geography, precision, option)
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geography, int4, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGML($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+
+
+--
+-- KML OUTPUT
+--
+
+-- _ST_AsKML(version, geography, precision)
+CREATE OR REPLACE FUNCTION _ST_AsKML(int4, geography, int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_kml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- AsKML(geography,precision) / version=2
+CREATE OR REPLACE FUNCTION ST_AsKML(geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsKML(2, $1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- AsKML(geography) / precision=15 version=2
+CREATE OR REPLACE FUNCTION ST_AsKML(geography)
+	RETURNS text
+	AS 'SELECT _ST_AsKML(2, $1, 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsKML(text)
+	RETURNS text AS
+	$$ SELECT ST_AsKML($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsKML(version, geography) / precision=15 
+CREATE OR REPLACE FUNCTION ST_AsKML(int4, geography)
+	RETURNS text
+	AS 'SELECT _ST_AsKML($1, $2, 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsKML(version, geography, precision)
+CREATE OR REPLACE FUNCTION ST_AsKML(int4, geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsKML($1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+
+
+--
+-- GeoJson Output
+--
+
+CREATE OR REPLACE FUNCTION _ST_AsGeoJson(int4, geography, int4, int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_geojson'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geography, precision) / version=1 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson(1, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geography) / precision=15 version=1 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geography)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson(1, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(text)
+	RETURNS text AS
+	$$ SELECT ST_AsGeoJson($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geography) / precision=15 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geography)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson($1, $2, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geography, precision) / options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geography, precision, options) / version=1
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geography, int4, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson(1, $1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geography, precision,options)
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geography, int4, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+-- Measurement Functions
+-- Availability: 1.5.0
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+-- Stop calculation and return immediately once distance is less than tolerance
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_Distance(geography, geography, float8, boolean)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','geography_distance'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Stop calculation and return immediately once distance is less than tolerance
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_DWithin(geography, geography, float8, boolean)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','geography_dwithin'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Distance(geography, geography, boolean)
+	RETURNS float8
+	AS 'SELECT _ST_Distance($1, $2, 0.0, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Currently defaulting to spheroid calculations
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Distance(geography, geography)
+	RETURNS float8
+	AS 'SELECT _ST_Distance($1, $2, 0.0, true)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+	
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Distance(text, text)
+	RETURNS float8 AS
+	$$ SELECT ST_Distance($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Only expands the bounding box, the actual geometry will remain unchanged, use with care.
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_Expand(geography, float8)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_DWithin(geography, geography, float8, boolean)
+	RETURNS boolean
+	AS 'SELECT $1 && _ST_Expand($2,$3) AND $2 && _ST_Expand($1,$3) AND _ST_DWithin($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Currently defaulting to spheroid calculations
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_DWithin(geography, geography, float8)
+	RETURNS boolean
+	AS 'SELECT $1 && _ST_Expand($2,$3) AND $2 && _ST_Expand($1,$3) AND _ST_DWithin($1, $2, $3, true)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_DWithin(text, text, float8)
+	RETURNS boolean AS
+	$$ SELECT ST_DWithin($1::geometry, $2::geometry, $3);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Area(geography, boolean)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','geography_area'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Currently defaulting to spheroid calculations
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Area(geography)
+	RETURNS float8
+	AS 'SELECT ST_Area($1, true)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Area(text)
+	RETURNS float8 AS
+	$$ SELECT ST_Area($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Length(geography, boolean)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','geography_length'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Length(geography)
+	RETURNS float8
+	AS 'SELECT ST_Length($1, true)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Length(text)
+	RETURNS float8 AS
+	$$ SELECT ST_Length($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_PointOutside(geography)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_point_outside'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Only implemented for polygon-over-point
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_Covers(geography, geography)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','geography_covers'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Only implemented for polygon-over-point
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Covers(geography, geography)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Covers($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Covers(text, text)
+	RETURNS boolean AS
+	$$ SELECT ST_Covers($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Only implemented for polygon-over-point
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_CoveredBy(geography, geography)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Covers($2, $1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_CoveredBy(text, text)
+	RETURNS boolean AS
+	$$ SELECT ST_CoveredBy($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Intersects(geography, geography)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Distance($1, $2, 0.0, false) < 0.00001'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Intersects(text, text)
+	RETURNS boolean AS
+	$$ SELECT ST_Intersects($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_BestSRID(geography, geography)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','geography_bestsrid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_BestSRID(geography)
+	RETURNS integer
+	AS 'SELECT _ST_BestSRID($1,$1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Buffer(geography, float8)
+	RETURNS geography
+	AS 'SELECT geography(ST_Transform(ST_Buffer(ST_Transform(geometry($1), _ST_BestSRID($1)), $2), 4326))'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Buffer(text, float8)
+	RETURNS geometry AS
+	$$ SELECT ST_Buffer($1::geometry, $2);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Intersection(geography, geography)
+	RETURNS geography
+	AS 'SELECT geography(ST_Transform(ST_Intersection(ST_Transform(geometry($1), _ST_BestSRID($1, $2)), ST_Transform(geometry($2), _ST_BestSRID($1, $2))), 4326))'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Intersection(text, text)
+	RETURNS geometry AS
+	$$ SELECT ST_Intersection($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+---------------------------------------------------------------
+-- SQL-MM
+---------------------------------------------------------------
+
+--
+-- SQL-MM
+--
+-- ST_CurveToLine(Geometry geometry, SegmentsPerQuarter integer)
+--
+-- Converts a given geometry to a linear geometry.  Each curveed
+-- geometry or segment is converted into a linear approximation using
+-- the given number of segments per quarter circle.
+CREATE OR REPLACE FUNCTION ST_CurveToLine(geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_curve_segmentize'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+--
+-- SQL-MM
+--
+-- ST_CurveToLine(Geometry geometry, SegmentsPerQuarter integer)
+--
+-- Converts a given geometry to a linear geometry.  Each curveed
+-- geometry or segment is converted into a linear approximation using
+-- the default value of 32 segments per quarter circle
+CREATE OR REPLACE FUNCTION ST_CurveToLine(geometry)
+	RETURNS geometry AS 'SELECT ST_CurveToLine($1, 32)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_HasArc(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_has_arc'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_LineToCurve(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_desegmentize'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+---------------------------------------------------------------
+-- END
+---------------------------------------------------------------
+
+
+---------------------------------------------------------------
+-- USER CONTRIUBUTED
+---------------------------------------------------------------
+
+-----------------------------------------------------------------------
+-- ST_MinimumBoundingCircle(inputgeom geometry, segs_per_quarter integer)
+-----------------------------------------------------------------------
+-- Returns the smallest circle polygon that can fully contain a geometry
+-- Defaults to 48 segs per quarter to approximate a circle
+-- Contributed by Bruce Rindahl
+-- Availability: 1.4.0
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION ST_MinimumBoundingCircle(inputgeom geometry, segs_per_quarter integer)
+	RETURNS geometry AS
+$BODY$
+	DECLARE
+	hull GEOMETRY;
+	ring GEOMETRY;
+	center GEOMETRY;
+	radius DOUBLE PRECISION;
+	dist DOUBLE PRECISION;
+	d DOUBLE PRECISION;
+	idx1 integer;
+	idx2 integer;
+	l1 GEOMETRY;
+	l2 GEOMETRY;
+	p1 GEOMETRY;
+	p2 GEOMETRY;
+	a1 DOUBLE PRECISION;
+	a2 DOUBLE PRECISION;
+
+
+	BEGIN
+
+	-- First compute the ConvexHull of the geometry
+	hull = ST_ConvexHull(inputgeom);
+	--A point really has no MBC
+	IF ST_GeometryType(hull) = 'ST_Point' THEN
+		RETURN hull;
+	END IF;
+	-- convert the hull perimeter to a linestring so we can manipulate individual points
+	--If its already a linestring force it to a closed linestring
+	ring = CASE WHEN ST_GeometryType(hull) = 'ST_LineString' THEN ST_AddPoint(hull, ST_StartPoint(hull)) ELSE ST_ExteriorRing(hull) END;
+
+	dist = 0;
+	-- Brute Force - check every pair
+	FOR i in 1 .. (ST_NumPoints(ring)-2)
+		LOOP
+			FOR j in i .. (ST_NumPoints(ring)-1)
+				LOOP
+				d = ST_Distance(ST_PointN(ring,i),ST_PointN(ring,j));
+				-- Check the distance and update if larger
+				IF (d > dist) THEN
+					dist = d;
+					idx1 = i;
+					idx2 = j;
+				END IF;
+			END LOOP;
+		END LOOP;
+
+	-- We now have the diameter of the convex hull.  The following line returns it if desired.
+	-- RETURN MakeLine(PointN(ring,idx1),PointN(ring,idx2));
+
+	-- Now for the Minimum Bounding Circle.  Since we know the two points furthest from each
+	-- other, the MBC must go through those two points. Start with those points as a diameter of a circle.
+
+	-- The radius is half the distance between them and the center is midway between them
+	radius = ST_Distance(ST_PointN(ring,idx1),ST_PointN(ring,idx2)) / 2.0;
+	center = ST_Line_interpolate_point(ST_MakeLine(ST_PointN(ring,idx1),ST_PointN(ring,idx2)),0.5);
+
+	-- Loop through each vertex and check if the distance from the center to the point
+	-- is greater than the current radius.
+	FOR k in 1 .. (ST_NumPoints(ring)-1)
+		LOOP
+		IF(k <> idx1 and k <> idx2) THEN
+			dist = ST_Distance(center,ST_PointN(ring,k));
+			IF (dist > radius) THEN
+				-- We have to expand the circle.  The new circle must pass trhough
+				-- three points - the two original diameters and this point.
+
+				-- Draw a line from the first diameter to this point
+				l1 = ST_Makeline(ST_PointN(ring,idx1),ST_PointN(ring,k));
+				-- Compute the midpoint
+				p1 = ST_line_interpolate_point(l1,0.5);
+				-- Rotate the line 90 degrees around the midpoint (perpendicular bisector)
+				l1 = ST_Translate(ST_Rotate(ST_Translate(l1,-X(p1),-Y(p1)),pi()/2),X(p1),Y(p1));
+				--  Compute the azimuth of the bisector
+				a1 = ST_Azimuth(ST_PointN(l1,1),ST_PointN(l1,2));
+				--  Extend the line in each direction the new computed distance to insure they will intersect
+				l1 = ST_AddPoint(l1,ST_Makepoint(X(ST_PointN(l1,2))+sin(a1)*dist,Y(ST_PointN(l1,2))+cos(a1)*dist),-1);
+				l1 = ST_AddPoint(l1,ST_Makepoint(X(ST_PointN(l1,1))-sin(a1)*dist,Y(ST_PointN(l1,1))-cos(a1)*dist),0);
+
+				-- Repeat for the line from the point to the other diameter point
+				l2 = ST_Makeline(ST_PointN(ring,idx2),ST_PointN(ring,k));
+				p2 = ST_Line_interpolate_point(l2,0.5);
+				l2 = ST_Translate(ST_Rotate(ST_Translate(l2,-X(p2),-Y(p2)),pi()/2),X(p2),Y(p2));
+				a2 = ST_Azimuth(ST_PointN(l2,1),ST_PointN(l2,2));
+				l2 = ST_AddPoint(l2,ST_Makepoint(X(ST_PointN(l2,2))+sin(a2)*dist,Y(ST_PointN(l2,2))+cos(a2)*dist),-1);
+				l2 = ST_AddPoint(l2,ST_Makepoint(X(ST_PointN(l2,1))-sin(a2)*dist,Y(ST_PointN(l2,1))-cos(a2)*dist),0);
+
+				-- The new center is the intersection of the two bisectors
+				center = ST_Intersection(l1,l2);
+				-- The new radius is the distance to any of the three points
+				radius = ST_Distance(center,ST_PointN(ring,idx1));
+			END IF;
+		END IF;
+		END LOOP;
+	--DONE!!  Return the MBC via the buffer command
+	RETURN ST_Buffer(center,radius,segs_per_quarter);
+
+	END;
+$BODY$
+	LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_MinimumBoundingCircle(geometry)
+ RETURNS geometry AS
+'SELECT ST_MinimumBoundingCircle($1, 48)'
+ LANGUAGE 'sql' IMMUTABLE STRICT;
+COMMIT;
+
+
+-- First drop old aggregates
+DROP AGGREGATE IF EXISTS geomunion(geometry);
+DROP AGGREGATE IF EXISTS st_geomunion(geometry);
+DROP AGGREGATE IF EXISTS accum_old(geometry);
+DROP AGGREGATE IF EXISTS st_accum_old(geometry);
+
+-- Then drop old functions
+DROP FUNCTION IF EXISTS box2d_overleft(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_overright(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_left(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_right(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_contain(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_contained(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_overlap(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_same(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_intersects(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_overleft(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_overright(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_left(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_right(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_contain(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_contained(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_overlap(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_same(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_intersects(box2d, box2d);
+DROP FUNCTION IF EXISTS st_addbbox(geometry);
+DROP FUNCTION IF EXISTS st_dropbbox(geometry); 
+DROP FUNCTION IF EXISTS st_hasbbox(geometry); 
+DROP FUNCTION IF EXISTS cache_bbox();
+DROP FUNCTION IF EXISTS st_cache_bbox();
+DROP FUNCTION IF EXISTS transform_geometry(geometry,text,text,int);
+DROP FUNCTION IF EXISTS collector(geometry, geometry);
+DROP FUNCTION IF EXISTS st_collector(geometry, geometry);
+DROP FUNCTION IF EXISTS geom_accum (geometry[],geometry);
+DROP FUNCTION IF EXISTS st_geom_accum (geometry[],geometry);
+DROP FUNCTION IF EXISTS collect_garray (geometry[]);
+DROP FUNCTION IF EXISTS st_collect_garray (geometry[]);
+DROP FUNCTION IF EXISTS geosnoop(geometry);
+DROP FUNCTION IF EXISTS jtsnoop(geometry);
+DROP FUNCTION IF EXISTS st_noop(geometry);
+DROP FUNCTION IF EXISTS st_max_distance(geometry, geometry);
+
+
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/all-wcprops
@@ -1,1 +1,42 @@
+K 25
+svn:wc:ra_dav:version-url
+V 22
+/svn/!svn/ver/20/trunk
+END
+RollingCurlGroup.php
+K 25
+svn:wc:ra_dav:version-url
+V 43
+/svn/!svn/ver/20/trunk/RollingCurlGroup.php
+END
+example_groups.php
+K 25
+svn:wc:ra_dav:version-url
+V 41
+/svn/!svn/ver/20/trunk/example_groups.php
+END
+example.php
+K 25
+svn:wc:ra_dav:version-url
+V 34
+/svn/!svn/ver/20/trunk/example.php
+END
+RollingCurl.php
+K 25
+svn:wc:ra_dav:version-url
+V 38
+/svn/!svn/ver/20/trunk/RollingCurl.php
+END
+CHANGELOG.txt
+K 25
+svn:wc:ra_dav:version-url
+V 36
+/svn/!svn/ver/20/trunk/CHANGELOG.txt
+END
+README.txt
+K 25
+svn:wc:ra_dav:version-url
+V 33
+/svn/!svn/ver/20/trunk/README.txt
+END
 

--- /dev/null
+++ b/lib/rolling-curl/.svn/entries
@@ -1,1 +1,233 @@
-
+10
+
+dir
+20
+http://rolling-curl.googlecode.com/svn/trunk
+http://rolling-curl.googlecode.com/svn
+
+
+
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+74aa2acc-2e27-11de-b2a4-4f96ceaaac44
+
+RollingCurlGroup.php
+file
+
+
+
+
+2011-04-10T08:32:48.081650Z
+73c08d9e9e24b4adc89816624c7aca30
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5152
+
+example_groups.php
+file
+
+
+
+
+2011-04-10T08:32:48.082650Z
+907ed82a47d346c39acbd5578e1d0230
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1367
+
+example.php
+file
+
+
+
+
+2011-04-10T08:32:48.083650Z
+87aa845abfaffc09ed4eca024f2a8b8a
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1860
+
+RollingCurl.php
+file
+
+
+
+
+2011-04-10T08:32:48.084650Z
+205391c449f3f3ee050004dadc374dc8
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+10444
+
+CHANGELOG.txt
+file
+
+
+
+
+2011-04-10T08:32:48.085650Z
+d0452f6f9530ed04580159121d0fd5f7
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+662
+
+README.txt
+file
+
+
+
+
+2011-04-10T08:32:48.085650Z
+60dd357081431c0f2b82989cdbce8615
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6355
+
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/CHANGELOG.txt.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
 

--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/RollingCurlGroup.php.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
 

--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/example_groups.php.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
 

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/CHANGELOG.txt.svn-base
@@ -1,1 +1,15 @@
+Rolling Curl changelog
+======================
 
+September 13, 2010
+------------------
+- Bug #12, #14: Fixed default options overriding (LionsAd)
+- Bug #10: Added use of curl_multi_select to avoid burning CPU (LionsAd)
+- Enh #6, #9: Added $request as parameter to callback function (LionsAd)
+- Chg: Request renamed to RollingCurlRequest (LionsAd)
+- Added RollingCurlGroup class that allows processing groups of requests (LionsAd)
+- More cleanup at unsetting a class (LionsAd)
+- Timeout parameter for curl_multi_select is now configurable (LionsAd)
+- single_curl now returns true (LionsAd)
+- Readme corrections (Alexander Makarov)
+- Code cleanup (Alexander Makarov)

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/README.txt.svn-base
@@ -1,1 +1,210 @@
-
+Rolling Curl
+============
+
+RollingCurl allows you to process multiple HTTP requests in parallel using CURL PHP library.
+
+Released under the Apache License 2.0.
+
+Authors
+-------
+- Was originally written by [Josh Fraser](joshfraser.com).
+- Currently maintained by [Alexander Makarov](http://rmcreative.ru/).
+- Received significant updates and patched from [LionsAd](http://github.com/LionsAd/rolling-curl).
+
+Overview
+--------
+RollingCurl is a more efficient implementation of curl_multi() curl_multi is a great way to process multiple HTTP requests in parallel in PHP.
+curl_multi is particularly handy when working with large data sets (like fetching thousands of RSS feeds at one time). Unfortunately there is
+very little documentation on the best way to implement curl_multi. As a result, most of the examples around the web are either inefficient or
+fail entirely when asked to handle more than a few hundred requests.
+
+The problem is that most implementations of curl_multi wait for each set of requests to complete before processing them. If there are too many requests
+to process at once, they usually get broken into groups that are then processed one at a time. The problem with this is that each group has to wait for
+the slowest request to download. In a group of 100 requests, all it takes is one slow one to delay the processing of 99 others. The larger the number of
+requests you are dealing with, the more noticeable this latency becomes.
+
+The solution is to process each request as soon as it completes. This eliminates the wasted CPU cycles from busy waiting. Also there is a queue of
+cURL requests to allow for maximum throughput. Each time a request is completed, a new one is added from the queue. By dynamically adding and removing
+links, we keep a constant number of links downloading at all times. This gives us a way to throttle the amount of simultaneous requests we are sending.
+The result is a faster and more efficient way of processing large quantities of cURL requests in parallel.
+
+Callbacks
+---------
+
+Each of requests usually do have a callback to process results that is being executed when request is done
+(both successfully or not).
+
+Callback accepts three parameters and can look like the following one:
+~~~
+[php]
+function request_callback($response, $info, $request){
+    // doing something with the data received
+}
+~~~
+
+- $response contains received page body.
+- $info is an associative array that holds various information about response such as HTTP response code, content type,
+time taken to make request etc.
+- $request contains RollingCurlRequest that was used to make request.
+
+Examples
+--------
+### Hello world
+
+~~~
+[php]
+// an array of URL's to fetch
+$urls = array("http://www.google.com",
+              "http://www.facebook.com",
+              "http://www.yahoo.com");
+
+// a function that will process the returned responses
+function request_callback($response, $info, $request) {
+	// parse the page title out of the returned HTML
+	if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
+		$title = $out[1];
+	}
+	echo "<b>$title</b><br />";
+	print_r($info);
+	echo "<hr>";
+}
+
+// create a new RollingCurl object and pass it the name of your custom callback function
+$rc = new RollingCurl("request_callback");
+// the window size determines how many simultaneous requests to allow.
+$rc->window_size = 20;
+foreach ($urls as $url) {
+    // add each request to the RollingCurl object
+    $request = new RollingCurlRequest($url);
+    $rc->add($request);
+}
+$rc->execute();
+~~~
+
+
+### Setting custom options
+
+Set custom options for EVERY request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->execute();
+~~~
+
+Set custom options for A SINGLE request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$request = new RollingCurlRequest($url);
+$request->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->add($request);
+$rc->execute();
+~~~
+
+### Shortcuts
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->get("http://www.google.com");
+$rc->get("http://www.yahoo.com");
+$rc->execute();
+~~~
+
+### Class callbacks
+
+~~~
+[php]
+class MyInfoCollector {
+    private $rc;
+
+    function __construct(){
+        $this->rc = new RollingCurl(array($this, 'processPage'));
+    }
+
+    function processPage($response, $info, $request){
+      //...
+    }
+
+    function run($urls){
+        foreach ($urls as $url){
+            $request = new RollingCurlRequest($url);
+            $this->rc->add($request);
+        }
+        $this->rc->execute();
+    }
+}
+
+$collector = new MyInfoCollector();
+$collector->run(array(
+    'http://google.com/',
+    'http://yahoo.com/'
+));
+~~~
+
+### Using RollingCurlGroup
+
+~~~
+[php]
+class TestCurlRequest extends RollingCurlGroupRequest {
+    public $test_verbose = true;
+
+    function process($output, $info) {
+        echo "Processing " . $this->url . "\n";
+        if ($this->test_verbose)
+            print_r($info);
+
+        parent::process($output, $info);
+    }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+    function process($output, $info, $request) {
+        echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+        parent::process($output, $info, $request);
+    }
+
+    function finished() {
+        echo "Group CB: Finished" . $this->name . "\n";
+        parent::finished();
+    }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+$rc->add($req);
+
+$rc->execute();
+~~~
+
+The same function (add) can be used both for adding requests and groups of requests.
+The "callback" in request and groups is:
+
+process($output, $info)
+
+and
+
+process($output, $info, $request)
+
+Also you can override RollingCurlGroup::finished() that will be executed right after finishing group processing.
+
+$Id$

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/RollingCurl.php.svn-base
@@ -1,1 +1,375 @@
-
+<?php
+/*
+Authored by Josh Fraser (www.joshfraser.com)
+Released under Apache License 2.0
+
+Maintained by Alexander Makarov, http://rmcreative.ru/
+
+$Id$
+*/
+
+/**
+ * Class that represent a single curl request
+ */
+class RollingCurlRequest {
+    public $url = false;
+    public $method = 'GET';
+    public $post_data = null;
+    public $headers = null;
+    public $options = null;
+
+    /**
+     * @param string $url
+     * @param string $method
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return void
+     */
+    function __construct($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+        $this->url = $url;
+        $this->method = $method;
+        $this->post_data = $post_data;
+        $this->headers = $headers;
+        $this->options = $options;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->url, $this->method, $this->post_data, $this->headers, $this->options);
+    }
+}
+
+/**
+ * RollingCurl custom exception
+ */
+class RollingCurlException extends Exception {
+}
+
+/**
+ * Class that holds a rolling queue of curl requests.
+ *
+ * @throws RollingCurlException
+ */
+class RollingCurl {
+    /**
+     * @var int
+     *
+     * Window size is the max number of simultaneous connections allowed.
+     *
+     * REMEMBER TO RESPECT THE SERVERS:
+     * Sending too many requests at one time can easily be perceived
+     * as a DOS attack. Increase this window_size if you are making requests
+     * to multiple servers or have permission from the receving server admins.
+     */
+    private $window_size = 5;
+
+    /**
+     * @var float
+     *
+     * Timeout is the timeout used for curl_multi_select.
+     */
+    private $timeout = 10;
+
+    /**
+     * @var string|array
+     *
+     * Callback function to be applied to each result.
+     */
+    private $callback;
+
+    /**
+     * @var array
+     *
+     * Set your base options that you want to be used with EVERY request.
+     */
+    protected $options = array(
+        CURLOPT_SSL_VERIFYPEER => 0,
+        CURLOPT_RETURNTRANSFER => 1,
+        CURLOPT_CONNECTTIMEOUT => 30,
+        CURLOPT_TIMEOUT => 30
+    );
+
+    /**
+     * @var array
+     */
+    private $headers = array();
+
+    /**
+     * @var Request[]
+     *
+     * The request queue
+     */
+    private $requests = array();
+
+    /**
+     * @var RequestMap[]
+     *
+     * Maps handles to request indexes
+     */
+    private $requestMap = array();
+
+    /**
+     * @param  $callback
+     * Callback function to be applied to each result.
+     *
+     * Can be specified as 'my_callback_function'
+     * or array($object, 'my_callback_method').
+     *
+     * Function should take three parameters: $response, $info, $request.
+     * $response is response body, $info is additional curl info.
+     * $request is the original request
+     *
+     * @return void
+     */
+    function __construct($callback = null) {
+        $this->callback = $callback;
+    }
+
+    /**
+     * @param string $name
+     * @return mixed
+     */
+    public function __get($name) {
+        return (isset($this->{$name})) ? $this->{$name} : null;
+    }
+
+    /**
+     * @param string $name
+     * @param mixed $value
+     * @return bool
+     */
+    public function __set($name, $value) {
+        // append the base options & headers
+        if ($name == "options" || $name == "headers") {
+            $this->{$name} = $value + $this->{$name};
+        } else {
+            $this->{$name} = $value;
+        }
+        return true;
+    }
+
+    /**
+     * Add a request to the request queue
+     *
+     * @param Request $request
+     * @return bool
+     */
+    public function add($request) {
+        $this->requests[] = $request;
+        return true;
+    }
+
+    /**
+     * Create new Request and add it to the request queue
+     *
+     * @param string $url
+     * @param string $method
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+        $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options);
+        return true;
+    }
+
+    /**
+     * Perform GET request
+     *
+     * @param string $url
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function get($url, $headers = null, $options = null) {
+        return $this->request($url, "GET", null, $headers, $options);
+    }
+
+    /**
+     * Perform POST request
+     *
+     * @param string $url
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function post($url, $post_data = null, $headers = null, $options = null) {
+        return $this->request($url, "POST", $post_data, $headers, $options);
+    }
+
+    /**
+     * Execute processing
+     *
+     * @param int $window_size Max number of simultaneous connections
+     * @return string|bool
+     */
+    public function execute($window_size = null) {
+        // rolling curl window must always be greater than 1
+        if (sizeof($this->requests) == 1) {
+            return $this->single_curl();
+        } else {
+            // start the rolling curl. window_size is the max number of simultaneous connections
+            return $this->rolling_curl($window_size);
+        }
+    }
+
+    /**
+     * Performs a single curl request
+     *
+     * @access private
+     * @return string
+     */
+    private function single_curl() {
+        $ch = curl_init();
+        $request = array_shift($this->requests);
+        $options = $this->get_options($request);
+        curl_setopt_array($ch, $options);
+        $output = curl_exec($ch);
+        $info = curl_getinfo($ch);
+
+        // it's not neccesary to set a callback for one-off requests
+        if ($this->callback) {
+            $callback = $this->callback;
+            if (is_callable($this->callback)) {
+                call_user_func($callback, $output, $info, $request);
+            }
+        }
+        else
+            return $output;
+        return true;
+    }
+
+    /**
+     * Performs multiple curl requests
+     *
+     * @access private
+     * @throws RollingCurlException
+     * @param int $window_size Max number of simultaneous connections
+     * @return bool
+     */
+    private function rolling_curl($window_size = null) {
+        if ($window_size)
+            $this->window_size = $window_size;
+
+        // make sure the rolling window isn't greater than the # of urls
+        if (sizeof($this->requests) < $this->window_size)
+            $this->window_size = sizeof($this->requests);
+
+        if ($this->window_size < 2) {
+            throw new RollingCurlException("Window size must be greater than 1");
+        }
+
+        $master = curl_multi_init();
+
+        // start the first batch of requests
+        for ($i = 0; $i < $this->window_size; $i++) {
+            $ch = curl_init();
+
+            $options = $this->get_options($this->requests[$i]);
+
+            curl_setopt_array($ch, $options);
+            curl_multi_add_handle($master, $ch);
+
+            // Add to our request Maps
+            $key = (string) $ch;
+            $this->requestMap[$key] = $i;
+        }
+
+        do {
+            while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ;
+            if ($execrun != CURLM_OK)
+                break;
+            // a request was just completed -- find out which one
+            while ($done = curl_multi_info_read($master)) {
+
+                // get the info and content returned on the request
+                $info = curl_getinfo($done['handle']);
+                $output = curl_multi_getcontent($done['handle']);
+
+                // send the return values to the callback function.
+                $callback = $this->callback;
+                if (is_callable($callback)) {
+                    $key = (string) $done['handle'];
+                    $request = $this->requests[$this->requestMap[$key]];
+                    unset($this->requestMap[$key]);
+                    call_user_func($callback, $output, $info, $request);
+                }
+
+                // start a new request (it's important to do this before removing the old one)
+                if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) {
+                    $ch = curl_init();
+                    $options = $this->get_options($this->requests[$i]);
+                    curl_setopt_array($ch, $options);
+                    curl_multi_add_handle($master, $ch);
+
+                    // Add to our request Maps
+                    $key = (string) $ch;
+                    $this->requestMap[$key] = $i;
+                    $i++;
+                }
+
+                // remove the curl handle that just completed
+                curl_multi_remove_handle($master, $done['handle']);
+
+            }
+
+            // Block for data in / output; error handling is done by curl_multi_exec
+            if ($running)
+                curl_multi_select($master, $this->timeout);
+
+        } while ($running);
+        curl_multi_close($master);
+        return true;
+    }
+
+
+    /**
+     * Helper function to set up a new request by setting the appropriate options
+     *
+     * @access private
+     * @param Request $request
+     * @return array
+     */
+    private function get_options($request) {
+        // options for this entire curl object
+        $options = $this->__get('options');
+        if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) {
+            $options[CURLOPT_FOLLOWLOCATION] = 1;
+            $options[CURLOPT_MAXREDIRS] = 5;
+        }
+        $headers = $this->__get('headers');
+
+        // append custom options for this specific request
+        if ($request->options) {
+            $options = $request->options + $options;
+        }
+
+        // set the request URL
+        $options[CURLOPT_URL] = $request->url;
+
+        // posting data w/ this request?
+        if ($request->post_data) {
+            $options[CURLOPT_POST] = 1;
+            $options[CURLOPT_POSTFIELDS] = $request->post_data;
+        }
+        if ($headers) {
+            $options[CURLOPT_HEADER] = 0;
+            $options[CURLOPT_HTTPHEADER] = $headers;
+        }
+
+        return $options;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests);
+    }
+}
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/RollingCurlGroup.php.svn-base
@@ -1,1 +1,218 @@
-
+<?php
+/*
+
+  Authored by Fabian Franz (www.lionsad.de)
+  Released under Apache License 2.0
+
+$Id$
+*/
+
+class RollingCurlGroupException extends Exception {}
+
+/**
+ * @throws RollingCurlGroupException
+ */
+abstract class RollingCurlGroupRequest extends RollingCurlRequest {
+    private $group = null;
+
+    /**
+     * Set group for this request
+     *
+     * @param group The group to be set
+     */
+    function setGroup($group) {
+        if (!($group instanceof RollingCurlGroup))
+            throw new RollingCurlGroupException("setGroup: group needs to be of instance RollingCurlGroup");
+
+        $this->group = $group;
+    }
+
+    /**
+     * Process the request
+     *
+     *
+     */
+    function process($output, $info) {
+        if ($this->group)
+            $this->group->process($output, $info, $this);
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->group);
+        parent::__destruct();
+    }
+
+}
+
+/**
+ * A group of curl requests.
+ *
+ * @throws RollingCurlGroupException *
+ */
+class RollingCurlGroup {
+    /**
+     * @var string group name
+     */
+    protected $name;
+
+    /**
+     * @var int total number of requests in a group
+     */
+    protected $num_requests = 0;
+
+    /**
+     * @var int total number of finished requests in a group
+     */
+    protected $finished_requests = 0;
+
+    /**
+     * @var array requests array
+     */
+    private $requests = array();
+
+    /**
+     * @param string $name group name
+     * @return void
+     */
+    function __construct($name) {
+        $this->name = $name;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->name, $this->num_requests, $this->finished_requests, $this->requests);
+    }
+
+    /**
+     * Adds request to a group
+     *
+     * @throws RollingCurlGroupException
+     * @param RollingCurlGroupRequest|array $request
+     * @return bool
+     */
+    function add($request) {
+        if ($request instanceof RollingCurlGroupRequest) {
+            $request->setGroup($this);
+            $this->num_requests++;
+            $this->requests[] = $request;
+        }
+        else if (is_array($request)) {
+            foreach ($request as $req)
+            $this->add($req);
+        }
+        else
+            throw new RollingCurlGroupException("add: Request needs to be of instance RollingCurlGroupRequest");
+
+        return true;
+    }
+
+    /**
+     * @throws RollingCurlGroupException
+     * @param RollingCurl $rc
+     * @return bool
+     */
+    function addToRC(RollingCurl $rc){
+        $ret = true;
+
+        while (count($this->requests) > 0){
+            $ret1 = $rc->add(array_shift($this->requests));
+            if (!$ret1)
+                $ret = false;
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Override to implement custom response processing.
+     *
+     * Don't forget to call parent::process().
+     *
+     * @param string $output received page body
+     * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+     * @param RollingCurlRequest $request request used
+     * @return void
+     */
+    function process($output, $info, $request) {
+        $this->finished_requests++;
+
+        if ($this->finished_requests >= $this->num_requests)
+            $this->finished();
+    }
+
+    /**
+     * Override to execute code after all requests in a group are processed.
+     *
+     * @return void
+     */
+    function finished() {
+    }
+
+}
+
+/**
+ * Group version of rolling curl
+ */
+class GroupRollingCurl extends RollingCurl {
+
+    /**
+     * @var mixed common callback for all groups
+     */
+    private $group_callback = null;
+
+    /**
+     * @param string $output received page body
+     * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+     * @param RollingCurlRequest $request request used
+     * @return void
+     */
+    protected function process($output, $info, $request) {
+        if ($request instanceof RollingCurlGroupRequest)
+            $request->process($output, $info);
+
+        if (is_callable($this->group_callback))
+            call_user_func($this->group_callback, $output, $info, $request);
+    }
+
+    /**
+     * @param mixed $callback common callback for all groups
+     * @return void
+     */
+    function __construct($callback = null) {
+        $this->group_callback = $callback;
+
+        parent::__construct(array(&$this, "process"));
+    }
+
+    /**
+     * Adds a group to processing queue
+     *
+     * @param RollingCurlGroup|Request $request
+     * @return bool
+     */
+    public function add($request) {
+        if ($request instanceof RollingCurlGroup)
+            return $request->addToRC($this);
+        else
+            return parent::add($request);
+    }
+
+    /**
+     * Execute processing
+     *
+     * @param int $window_size Max number of simultaneous connections
+     * @return bool|string
+     */
+    public function execute($window_size = null) {
+        if (count($this->requests) == 0)
+            return false;
+
+        return parent::execute($window_size);
+    }
+}
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/example.php.svn-base
@@ -1,1 +1,66 @@
+<?php
+/*
+authored by Josh Fraser (www.joshfraser.com)
+released under Apache License 2.0
 
+Maintained by Alexander Makarov, http://rmcreative.ru/
+
+$Id$
+*/
+
+require("RollingCurl.php");
+
+// a little example that fetches a bunch of sites in parallel and echos the page title and response info for each request
+function request_callback($response, $info, $request) {
+	// parse the page title out of the returned HTML
+	if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
+		$title = $out[1];
+	}
+	echo "<b>$title</b><br />";
+	print_r($info);
+    print_r($request);
+	echo "<hr>";
+}
+
+// single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.msn.com");
+$rc->execute();
+
+// another single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.google.com");
+$rc->execute();
+
+echo "<hr>";
+
+// top 20 sites according to alexa (11/5/09)
+$urls = array("http://www.google.com",
+              "http://www.facebook.com",
+              "http://www.yahoo.com",
+              "http://www.youtube.com",
+              "http://www.live.com",
+              "http://www.wikipedia.com",
+              "http://www.blogger.com",
+              "http://www.msn.com",
+              "http://www.baidu.com",
+              "http://www.yahoo.co.jp",
+              "http://www.myspace.com",
+              "http://www.qq.com",
+              "http://www.google.co.in",
+              "http://www.twitter.com",
+              "http://www.google.de",
+              "http://www.microsoft.com",
+              "http://www.google.cn",
+              "http://www.sina.com.cn",
+              "http://www.wordpress.com",
+              "http://www.google.co.uk");
+
+$rc = new RollingCurl("request_callback");
+$rc->window_size = 20;
+foreach ($urls as $url) {
+    $request = new RollingCurlRequest($url);
+    $rc->add($request);
+}
+$rc->execute();
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/example_groups.php.svn-base
@@ -1,1 +1,49 @@
+<?php
+require 'RollingCurl.php';
+require 'RollingCurlGroup.php';
 
+class TestCurlRequest extends RollingCurlGroupRequest {
+    public $test_verbose = true;
+
+    function process($output, $info) {
+        echo "Processing " . $this->url . "\n";
+        if ($this->test_verbose)
+            print_r($info);
+
+        parent::process($output, $info);
+    }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+    function process($output, $info, $request) {
+        echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+        parent::process($output, $info, $request);
+    }
+
+    function finished() {
+        echo "Group CB: Finished" . $this->name . "\n";
+        parent::finished();
+    }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+    $rc->add($req);
+
+$rc->execute();

--- /dev/null
+++ b/lib/rolling-curl/CHANGELOG.txt
@@ -1,1 +1,15 @@
+Rolling Curl changelog
+======================
 
+September 13, 2010
+------------------
+- Bug #12, #14: Fixed default options overriding (LionsAd)
+- Bug #10: Added use of curl_multi_select to avoid burning CPU (LionsAd)
+- Enh #6, #9: Added $request as parameter to callback function (LionsAd)
+- Chg: Request renamed to RollingCurlRequest (LionsAd)
+- Added RollingCurlGroup class that allows processing groups of requests (LionsAd)
+- More cleanup at unsetting a class (LionsAd)
+- Timeout parameter for curl_multi_select is now configurable (LionsAd)
+- single_curl now returns true (LionsAd)
+- Readme corrections (Alexander Makarov)
+- Code cleanup (Alexander Makarov)

--- /dev/null
+++ b/lib/rolling-curl/README.txt
@@ -1,1 +1,210 @@
-
+Rolling Curl
+============
+
+RollingCurl allows you to process multiple HTTP requests in parallel using CURL PHP library.
+
+Released under the Apache License 2.0.
+
+Authors
+-------
+- Was originally written by [Josh Fraser](joshfraser.com).
+- Currently maintained by [Alexander Makarov](http://rmcreative.ru/).
+- Received significant updates and patched from [LionsAd](http://github.com/LionsAd/rolling-curl).
+
+Overview
+--------
+RollingCurl is a more efficient implementation of curl_multi() curl_multi is a great way to process multiple HTTP requests in parallel in PHP.
+curl_multi is particularly handy when working with large data sets (like fetching thousands of RSS feeds at one time). Unfortunately there is
+very little documentation on the best way to implement curl_multi. As a result, most of the examples around the web are either inefficient or
+fail entirely when asked to handle more than a few hundred requests.
+
+The problem is that most implementations of curl_multi wait for each set of requests to complete before processing them. If there are too many requests
+to process at once, they usually get broken into groups that are then processed one at a time. The problem with this is that each group has to wait for
+the slowest request to download. In a group of 100 requests, all it takes is one slow one to delay the processing of 99 others. The larger the number of
+requests you are dealing with, the more noticeable this latency becomes.
+
+The solution is to process each request as soon as it completes. This eliminates the wasted CPU cycles from busy waiting. Also there is a queue of
+cURL requests to allow for maximum throughput. Each time a request is completed, a new one is added from the queue. By dynamically adding and removing
+links, we keep a constant number of links downloading at all times. This gives us a way to throttle the amount of simultaneous requests we are sending.
+The result is a faster and more efficient way of processing large quantities of cURL requests in parallel.
+
+Callbacks
+---------
+
+Each of requests usually do have a callback to process results that is being executed when request is done
+(both successfully or not).
+
+Callback accepts three parameters and can look like the following one:
+~~~
+[php]
+function request_callback($response, $info, $request){
+    // doing something with the data received
+}
+~~~
+
+- $response contains received page body.
+- $info is an associative array that holds various information about response such as HTTP response code, content type,
+time taken to make request etc.
+- $request contains RollingCurlRequest that was used to make request.
+
+Examples
+--------
+### Hello world
+
+~~~
+[php]
+// an array of URL's to fetch
+$urls = array("http://www.google.com",
+              "http://www.facebook.com",
+              "http://www.yahoo.com");
+
+// a function that will process the returned responses
+function request_callback($response, $info, $request) {
+	// parse the page title out of the returned HTML
+	if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
+		$title = $out[1];
+	}
+	echo "<b>$title</b><br />";
+	print_r($info);
+	echo "<hr>";
+}
+
+// create a new RollingCurl object and pass it the name of your custom callback function
+$rc = new RollingCurl("request_callback");
+// the window size determines how many simultaneous requests to allow.
+$rc->window_size = 20;
+foreach ($urls as $url) {
+    // add each request to the RollingCurl object
+    $request = new RollingCurlRequest($url);
+    $rc->add($request);
+}
+$rc->execute();
+~~~
+
+
+### Setting custom options
+
+Set custom options for EVERY request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->execute();
+~~~
+
+Set custom options for A SINGLE request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$request = new RollingCurlRequest($url);
+$request->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->add($request);
+$rc->execute();
+~~~
+
+### Shortcuts
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->get("http://www.google.com");
+$rc->get("http://www.yahoo.com");
+$rc->execute();
+~~~
+
+### Class callbacks
+
+~~~
+[php]
+class MyInfoCollector {
+    private $rc;
+
+    function __construct(){
+        $this->rc = new RollingCurl(array($this, 'processPage'));
+    }
+
+    function processPage($response, $info, $request){
+      //...
+    }
+
+    function run($urls){
+        foreach ($urls as $url){
+            $request = new RollingCurlRequest($url);
+            $this->rc->add($request);
+        }
+        $this->rc->execute();
+    }
+}
+
+$collector = new MyInfoCollector();
+$collector->run(array(
+    'http://google.com/',
+    'http://yahoo.com/'
+));
+~~~
+
+### Using RollingCurlGroup
+
+~~~
+[php]
+class TestCurlRequest extends RollingCurlGroupRequest {
+    public $test_verbose = true;
+
+    function process($output, $info) {
+        echo "Processing " . $this->url . "\n";
+        if ($this->test_verbose)
+            print_r($info);
+
+        parent::process($output, $info);
+    }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+    function process($output, $info, $request) {
+        echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+        parent::process($output, $info, $request);
+    }
+
+    function finished() {
+        echo "Group CB: Finished" . $this->name . "\n";
+        parent::finished();
+    }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+$rc->add($req);
+
+$rc->execute();
+~~~
+
+The same function (add) can be used both for adding requests and groups of requests.
+The "callback" in request and groups is:
+
+process($output, $info)
+
+and
+
+process($output, $info, $request)
+
+Also you can override RollingCurlGroup::finished() that will be executed right after finishing group processing.
+
+$Id$

--- /dev/null
+++ b/lib/rolling-curl/RollingCurl.php
@@ -1,1 +1,376 @@
-
+<?php
+/*
+Authored by Josh Fraser (www.joshfraser.com)
+Released under Apache License 2.0
+
+Maintained by Alexander Makarov, http://rmcreative.ru/
+
+$Id$
+*/
+
+/**
+ * Class that represent a single curl request
+ */
+class RollingCurlRequest {
+    public $url = false;
+    public $method = 'GET';
+    public $post_data = null;
+    public $headers = null;
+    public $options = null;
+    public $metadata = Array();
+    
+    /**
+     * @param string $url
+     * @param string $method
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return void
+     */
+    function __construct($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+        $this->url = $url;
+        $this->method = $method;
+        $this->post_data = $post_data;
+        $this->headers = $headers;
+        $this->options = $options;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->url, $this->method, $this->post_data, $this->headers, $this->options);
+    }
+}
+
+/**
+ * RollingCurl custom exception
+ */
+class RollingCurlException extends Exception {
+}
+
+/**
+ * Class that holds a rolling queue of curl requests.
+ *
+ * @throws RollingCurlException
+ */
+class RollingCurl {
+    /**
+     * @var int
+     *
+     * Window size is the max number of simultaneous connections allowed.
+     *
+     * REMEMBER TO RESPECT THE SERVERS:
+     * Sending too many requests at one time can easily be perceived
+     * as a DOS attack. Increase this window_size if you are making requests
+     * to multiple servers or have permission from the receving server admins.
+     */
+    private $window_size = 5;
+
+    /**
+     * @var float
+     *
+     * Timeout is the timeout used for curl_multi_select.
+     */
+    private $timeout = 10;
+
+    /**
+     * @var string|array
+     *
+     * Callback function to be applied to each result.
+     */
+    private $callback;
+
+    /**
+     * @var array
+     *
+     * Set your base options that you want to be used with EVERY request.
+     */
+    protected $options = array(
+        CURLOPT_SSL_VERIFYPEER => 0,
+        CURLOPT_RETURNTRANSFER => 1,
+        CURLOPT_CONNECTTIMEOUT => 60,
+        CURLOPT_TIMEOUT => 60
+    );
+
+    /**
+     * @var array
+     */
+    private $headers = array();
+
+    /**
+     * @var Request[]
+     *
+     * The request queue
+     */
+    private $requests = array();
+
+    /**
+     * @var RequestMap[]
+     *
+     * Maps handles to request indexes
+     */
+    private $requestMap = array();
+
+    /**
+     * @param  $callback
+     * Callback function to be applied to each result.
+     *
+     * Can be specified as 'my_callback_function'
+     * or array($object, 'my_callback_method').
+     *
+     * Function should take three parameters: $response, $info, $request.
+     * $response is response body, $info is additional curl info.
+     * $request is the original request
+     *
+     * @return void
+     */
+    function __construct($callback = null) {
+        $this->callback = $callback;
+    }
+
+    /**
+     * @param string $name
+     * @return mixed
+     */
+    public function __get($name) {
+        return (isset($this->{$name})) ? $this->{$name} : null;
+    }
+
+    /**
+     * @param string $name
+     * @param mixed $value
+     * @return bool
+     */
+    public function __set($name, $value) {
+        // append the base options & headers
+        if ($name == "options" || $name == "headers") {
+            $this->{$name} = $value + $this->{$name};
+        } else {
+            $this->{$name} = $value;
+        }
+        return true;
+    }
+
+    /**
+     * Add a request to the request queue
+     *
+     * @param Request $request
+     * @return bool
+     */
+    public function add($request) {
+        $this->requests[] = $request;
+        return true;
+    }
+
+    /**
+     * Create new Request and add it to the request queue
+     *
+     * @param string $url
+     * @param string $method
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+        $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options);
+        return true;
+    }
+
+    /**
+     * Perform GET request
+     *
+     * @param string $url
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function get($url, $headers = null, $options = null) {
+        return $this->request($url, "GET", null, $headers, $options);
+    }
+
+    /**
+     * Perform POST request
+     *
+     * @param string $url
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function post($url, $post_data = null, $headers = null, $options = null) {
+        return $this->request($url, "POST", $post_data, $headers, $options);
+    }
+
+    /**
+     * Execute processing
+     *
+     * @param int $window_size Max number of simultaneous connections
+     * @return string|bool
+     */
+    public function execute($window_size = null) {
+        // rolling curl window must always be greater than 1
+        if (sizeof($this->requests) == 1) {
+            return $this->single_curl();
+        } else {
+            // start the rolling curl. window_size is the max number of simultaneous connections
+            return $this->rolling_curl($window_size);
+        }
+    }
+
+    /**
+     * Performs a single curl request
+     *
+     * @access private
+     * @return string
+     */
+    private function single_curl() {
+        $ch = curl_init();
+        $request = array_shift($this->requests);
+        $options = $this->get_options($request);
+        curl_setopt_array($ch, $options);
+        $output = curl_exec($ch);
+        $info = curl_getinfo($ch);
+
+        // it's not neccesary to set a callback for one-off requests
+        if ($this->callback) {
+            $callback = $this->callback;
+            if (is_callable($this->callback)) {
+                call_user_func($callback, $output, $info, $request);
+            }
+        }
+        else
+            return $output;
+        return true;
+    }
+
+    /**
+     * Performs multiple curl requests
+     *
+     * @access private
+     * @throws RollingCurlException
+     * @param int $window_size Max number of simultaneous connections
+     * @return bool
+     */
+    private function rolling_curl($window_size = null) {
+        if ($window_size)
+            $this->window_size = $window_size;
+
+        // make sure the rolling window isn't greater than the # of urls
+        if (sizeof($this->requests) < $this->window_size)
+            $this->window_size = sizeof($this->requests);
+
+        if ($this->window_size < 2) {
+            throw new RollingCurlException("Window size must be greater than 1");
+        }
+
+        $master = curl_multi_init();
+
+        // start the first batch of requests
+        for ($i = 0; $i < $this->window_size; $i++) {
+            $ch = curl_init();
+
+            $options = $this->get_options($this->requests[$i]);
+
+            curl_setopt_array($ch, $options);
+            curl_multi_add_handle($master, $ch);
+
+            // Add to our request Maps
+            $key = (string) $ch;
+            $this->requestMap[$key] = $i;
+        }
+
+        do {
+            while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ;
+            if ($execrun != CURLM_OK)
+                break;
+            // a request was just completed -- find out which one
+            while ($done = curl_multi_info_read($master)) {
+
+                // get the info and content returned on the request
+                $info = curl_getinfo($done['handle']);
+                $output = curl_multi_getcontent($done['handle']);
+
+                // send the return values to the callback function.
+                $callback = $this->callback;
+                if (is_callable($callback)) {
+                    $key = (string) $done['handle'];
+                    $request = $this->requests[$this->requestMap[$key]];
+                    unset($this->requestMap[$key]);
+                    call_user_func($callback, $output, $info, $request);
+                }
+
+                // start a new request (it's important to do this before removing the old one)
+                if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) {
+                    $ch = curl_init();
+                    $options = $this->get_options($this->requests[$i]);
+                    curl_setopt_array($ch, $options);
+                    curl_multi_add_handle($master, $ch);
+
+                    // Add to our request Maps
+                    $key = (string) $ch;
+                    $this->requestMap[$key] = $i;
+                    $i++;
+                }
+
+                // remove the curl handle that just completed
+                curl_multi_remove_handle($master, $done['handle']);
+
+            }
+
+            // Block for data in / output; error handling is done by curl_multi_exec
+            if ($running)
+                curl_multi_select($master, $this->timeout);
+
+        } while ($running);
+        curl_multi_close($master);
+        return true;
+    }
+
+
+    /**
+     * Helper function to set up a new request by setting the appropriate options
+     *
+     * @access private
+     * @param Request $request
+     * @return array
+     */
+    private function get_options($request) {
+        // options for this entire curl object
+        $options = $this->__get('options');
+        if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) {
+            $options[CURLOPT_FOLLOWLOCATION] = 1;
+            $options[CURLOPT_MAXREDIRS] = 5;
+        }
+        $headers = $this->__get('headers');
+
+        // append custom options for this specific request
+        if ($request->options) {
+            $options = $request->options + $options;
+        }
+
+        // set the request URL
+        $options[CURLOPT_URL] = $request->url;
+
+        // posting data w/ this request?
+        if ($request->post_data) {
+            $options[CURLOPT_POST] = 1;
+            $options[CURLOPT_POSTFIELDS] = $request->post_data;
+        }
+        if ($headers) {
+            $options[CURLOPT_HEADER] = 0;
+            $options[CURLOPT_HTTPHEADER] = $headers;
+        }
+
+        return $options;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests);
+    }
+}
+

--- /dev/null
+++ b/lib/rolling-curl/RollingCurlGroup.php
@@ -1,1 +1,218 @@
-
+<?php
+/*
+
+  Authored by Fabian Franz (www.lionsad.de)
+  Released under Apache License 2.0
+
+$Id$
+*/
+
+class RollingCurlGroupException extends Exception {}
+
+/**
+ * @throws RollingCurlGroupException
+ */
+abstract class RollingCurlGroupRequest extends RollingCurlRequest {
+    private $group = null;
+
+    /**
+     * Set group for this request
+     *
+     * @param group The group to be set
+     */
+    function setGroup($group) {
+        if (!($group instanceof RollingCurlGroup))
+            throw new RollingCurlGroupException("setGroup: group needs to be of instance RollingCurlGroup");
+
+        $this->group = $group;
+    }
+
+    /**
+     * Process the request
+     *
+     *
+     */
+    function process($output, $info) {
+        if ($this->group)
+            $this->group->process($output, $info, $this);
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->group);
+        parent::__destruct();
+    }
+
+}
+
+/**
+ * A group of curl requests.
+ *
+ * @throws RollingCurlGroupException *
+ */
+class RollingCurlGroup {
+    /**
+     * @var string group name
+     */
+    protected $name;
+
+    /**
+     * @var int total number of requests in a group
+     */
+    protected $num_requests = 0;
+
+    /**
+     * @var int total number of finished requests in a group
+     */
+    protected $finished_requests = 0;
+
+    /**
+     * @var array requests array
+     */
+    private $requests = array();
+
+    /**
+     * @param string $name group name
+     * @return void
+     */
+    function __construct($name) {
+        $this->name = $name;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->name, $this->num_requests, $this->finished_requests, $this->requests);
+    }
+
+    /**
+     * Adds request to a group
+     *
+     * @throws RollingCurlGroupException
+     * @param RollingCurlGroupRequest|array $request
+     * @return bool
+     */
+    function add($request) {
+        if ($request instanceof RollingCurlGroupRequest) {
+            $request->setGroup($this);
+            $this->num_requests++;
+            $this->requests[] = $request;
+        }
+        else if (is_array($request)) {
+            foreach ($request as $req)
+            $this->add($req);
+        }
+        else
+            throw new RollingCurlGroupException("add: Request needs to be of instance RollingCurlGroupRequest");
+
+        return true;
+    }
+
+    /**
+     * @throws RollingCurlGroupException
+     * @param RollingCurl $rc
+     * @return bool
+     */
+    function addToRC(RollingCurl $rc){
+        $ret = true;
+
+        while (count($this->requests) > 0){
+            $ret1 = $rc->add(array_shift($this->requests));
+            if (!$ret1)
+                $ret = false;
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Override to implement custom response processing.
+     *
+     * Don't forget to call parent::process().
+     *
+     * @param string $output received page body
+     * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+     * @param RollingCurlRequest $request request used
+     * @return void
+     */
+    function process($output, $info, $request) {
+        $this->finished_requests++;
+
+        if ($this->finished_requests >= $this->num_requests)
+            $this->finished();
+    }
+
+    /**
+     * Override to execute code after all requests in a group are processed.
+     *
+     * @return void
+     */
+    function finished() {
+    }
+
+}
+
+/**
+ * Group version of rolling curl
+ */
+class GroupRollingCurl extends RollingCurl {
+
+    /**
+     * @var mixed common callback for all groups
+     */
+    private $group_callback = null;
+
+    /**
+     * @param string $output received page body
+     * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+     * @param RollingCurlRequest $request request used
+     * @return void
+     */
+    protected function process($output, $info, $request) {
+        if ($request instanceof RollingCurlGroupRequest)
+            $request->process($output, $info);
+
+        if (is_callable($this->group_callback))
+            call_user_func($this->group_callback, $output, $info, $request);
+    }
+
+    /**
+     * @param mixed $callback common callback for all groups
+     * @return void
+     */
+    function __construct($callback = null) {
+        $this->group_callback = $callback;
+
+        parent::__construct(array(&$this, "process"));
+    }
+
+    /**
+     * Adds a group to processing queue
+     *
+     * @param RollingCurlGroup|Request $request
+     * @return bool
+     */
+    public function add($request) {
+        if ($request instanceof RollingCurlGroup)
+            return $request->addToRC($this);
+        else
+            return parent::add($request);
+    }
+
+    /**
+     * Execute processing
+     *
+     * @param int $window_size Max number of simultaneous connections
+     * @return bool|string
+     */
+    public function execute($window_size = null) {
+        if (count($this->requests) == 0)
+            return false;
+
+        return parent::execute($window_size);
+    }
+}
+

--- /dev/null
+++ b/lib/rolling-curl/example.php
@@ -1,1 +1,66 @@
+<?php
+/*
+authored by Josh Fraser (www.joshfraser.com)
+released under Apache License 2.0
 
+Maintained by Alexander Makarov, http://rmcreative.ru/
+
+$Id$
+*/
+
+require("RollingCurl.php");
+
+// a little example that fetches a bunch of sites in parallel and echos the page title and response info for each request
+function request_callback($response, $info, $request) {
+	// parse the page title out of the returned HTML
+	if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
+		$title = $out[1];
+	}
+	echo "<b>$title</b><br />";
+	print_r($info);
+    print_r($request);
+	echo "<hr>";
+}
+
+// single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.msn.com");
+$rc->execute();
+
+// another single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.google.com");
+$rc->execute();
+
+echo "<hr>";
+
+// top 20 sites according to alexa (11/5/09)
+$urls = array("http://www.google.com",
+              "http://www.facebook.com",
+              "http://www.yahoo.com",
+              "http://www.youtube.com",
+              "http://www.live.com",
+              "http://www.wikipedia.com",
+              "http://www.blogger.com",
+              "http://www.msn.com",
+              "http://www.baidu.com",
+              "http://www.yahoo.co.jp",
+              "http://www.myspace.com",
+              "http://www.qq.com",
+              "http://www.google.co.in",
+              "http://www.twitter.com",
+              "http://www.google.de",
+              "http://www.microsoft.com",
+              "http://www.google.cn",
+              "http://www.sina.com.cn",
+              "http://www.wordpress.com",
+              "http://www.google.co.uk");
+
+$rc = new RollingCurl("request_callback");
+$rc->window_size = 20;
+foreach ($urls as $url) {
+    $request = new RollingCurlRequest($url);
+    $rc->add($request);
+}
+$rc->execute();
+

--- /dev/null
+++ b/lib/rolling-curl/example_groups.php
@@ -1,1 +1,49 @@
+<?php
+require 'RollingCurl.php';
+require 'RollingCurlGroup.php';
 
+class TestCurlRequest extends RollingCurlGroupRequest {
+    public $test_verbose = true;
+
+    function process($output, $info) {
+        echo "Processing " . $this->url . "\n";
+        if ($this->test_verbose)
+            print_r($info);
+
+        parent::process($output, $info);
+    }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+    function process($output, $info, $request) {
+        echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+        parent::process($output, $info, $request);
+    }
+
+    function finished() {
+        echo "Group CB: Finished" . $this->name . "\n";
+        parent::finished();
+    }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+    $rc->add($req);
+
+$rc->execute();

file:a/readme.txt -> file:b/readme.txt
--- a/readme.txt
+++ b/readme.txt
@@ -2,14 +2,32 @@
 Based on the maxious-canberra-transit-feed @ http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip
 Source code for the https://github.com/maxious/ACTBus-data transit 
 feed and https://github.com/maxious/ACTBus-ui this site available from github.
-Uses jQuery Mobile, PHP, Ruby, Python, Google Transit Feed Specification 
-tools, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder 
+Uses jQuery Mobile, PHP, PostgreSQL, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder 
 and Tile Service
 
-Must have view.sh running on port 8765 for this webapp to work
+See aws/awsStartup.sh for example startup steps. You need to load the included database dump; 
+for other transit networks you can use the updatedb.php script to load.
 
-For static maps, may have to do
+For openstreetmap static maps, may have to do
 /usr/sbin/setsebool -P httpd_can_network_connect=1
-on fedora
+on Fedora and other SELinux systems.
+
+To enter a service override, you can use the psql tool. eg.
+transitdata=# COPY calendar_dates (service_id, date, exception_type) FROM stdin;
+Enter data to be copied [spaced with tabs] followed by a newline.
+End with a backslash and a period on a line by itself.
+>> saturday	20110416	2 
+>> sunday	20110416	1
+>> saturday	20110423 	2
+>> sunday	20110423 	1
+>> weekday	20110425        2
+>> sunday	20110425        1
+>> weekday	20110422        2
+>> noservice    20110422        1
+>> weekday	20110426        2
+>> noservice    20110426        1
+>> sunday	20110424 	2
+>> noservice    20110424 	1
+>> \.
 
 

--- a/routeList.php
+++ b/routeList.php
@@ -7,99 +7,102 @@
 			<ul> 
 				<li><a href="routeList.php">By Final Destination...</a></li> 
 				<li><a href="routeList.php?bynumber=yes">By Number... </a></li>
-				<li><a href="routeList.php?bysuburb=yes">By Suburb... </a></li>
+				<li><a href="routeList.php?bysuburbs=yes">By Suburb... </a></li>
 				<li><a href="routeList.php?nearby=yes">Nearby... </a></li>
 			</ul>
                 </div>
 	';
 }
-if ($_REQUEST['bysuburb']) {
+if (isset($bysuburbs)) {
 	include_header("Routes by Suburb", "routeList");
 	navbar();
 	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
-	if (!isset($_REQUEST['firstLetter'])) {
+	if (!isset($firstLetter)) {
 		foreach (range('A', 'Z') as $letter) {
-			echo "<li><a href=\"routeList.php?firstLetter=$letter&bysuburb=yes\">$letter...</a></li>\n";
+			echo "<li><a href=\"routeList.php?firstLetter=$letter&amp;bysuburbs=yes\">$letter...</a></li>\n";
 		}
 	}
 	else {
 		foreach ($suburbs as $suburb) {
-			if (startsWith($suburb, $_REQUEST['firstLetter'])) {
+			if (startsWith($suburb, $firstLetter)) {
 				echo '<li><a href="routeList.php?suburb=' . urlencode($suburb) . '">' . $suburb . '</a></li>';
 			}
 		}
 	}
 	echo '</ul>';
 }
-else if ($_REQUEST['nearby'] || $_REQUEST['suburb']) {
-	if ($_REQUEST['suburb']) {
-		$suburb = filter_var($_REQUEST['suburb'], FILTER_SANITIZE_STRING);
-		$url = $APIurl . "/json/stopzonesearch?q=" . $suburb;
-		include_header("Routes by Suburb", "routeList");
+else if (isset($nearby) || isset($suburb)) {
+	$routes = Array();
+	if ($suburb) {
+		include_header($suburb . " - " . ucwords(service_period()) , "routeList");
+		navbar();
+		timePlaceSettings();
 		trackEvent("Route Lists", "Routes By Suburb", $suburb);
+		$routes = getRoutesbysuburbs($suburb);
 	}
-	if ($_REQUEST['nearby']) {
-		$url = $APIurl . "/json/neareststops?lat={$_SESSION['lat']}&lon={$_SESSION['lon']}&limit=15";
+	if (isset($nearby)) {
 		include_header("Routes Nearby", "routeList", true, true);
+		trackEvent("Route Lists", "Routes Nearby", $_SESSION['lat'] . "," . $_SESSION['lon']);
+		navbar();
 		timePlaceSettings(true);
 		if (!isset($_SESSION['lat']) || !isset($_SESSION['lat']) || $_SESSION['lat'] == "" || $_SESSION['lon'] == "") {
 			include_footer();
 			die();
 		}
+		$routes = getRoutesNearby($_SESSION['lat'], $_SESSION['lon']);
 	}
-	$stops = json_decode(getPage($url));
-	$routes = Array();
-	foreach ($stops as $stop) {
-		$url = $APIurl . "/json/stoproutes?stop=" . $stop[0];
-		$stoproutes = json_decode(getPage($url));
-		foreach ($stoproutes as $route) {
-			if (!isset($routes[$route[0]])) $routes[$route[0]] = $route;
+	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
+	if ($routes) {
+		foreach ($routes as $route) {
+			echo '<li><a href="trip.php?routeid=' . $route['route_id'] . '"><h3>' . $route['route_short_name'] . "</h3><p>" . $route['route_long_name'] . " (" . ucwords($route['service_id']) . ")</p>";
+			if (isset($nearby)) {
+				$time = getTimeInterpolatedRouteAtStop($route['route_id'], $route['stop_id']);
+				echo '<span class="ui-li-count">' . ($time['arrival_time'] ? $time['arrival_time'] : "No more trips today") . "<br>" . floor($route['distance']) . 'm away</span>';
+			}
+			echo "</a></li>\n";
 		}
 	}
-	navbar();
-	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
-	sksort($routes, 1, true);
-	foreach ($routes as $row) {
-		echo '<li>' . $row[1] . ' <a href="trip.php?routeid=' . $row[0] . '">' . $row[2] . " (" . ucwords($row[4]) . ")</a></li>\n";
+	else {
+		echo "<li style='text-align: center;'> No routes nearby.</li>";
 	}
 }
-else if ($_REQUEST['bynumber'] || $_REQUEST['numberSeries']) {
+else if (isset($bynumber) || isset($numberSeries)) {
 	include_header("Routes by Number", "routeList");
 	navbar();
 	echo ' <ul data-role="listview"  data-inset="true">';
-	$url = $APIurl . "/json/routes";
-	$contents = json_decode(getPage($url));
-	$routeSeries = Array();
-	$seriesRange = Array();
-	foreach ($contents as $key => $row) {
-		foreach (explode(" ", $row[1]) as $routeNumber) {
-			$seriesNum = substr($routeNumber, 0, -1) . "0";
-			if ($seriesNum == "0") $seriesNum = $routeNumber;
-			$finalDigit = substr($routeNumber, sizeof($routeNumber) - 1, 1);
-			if (isset($seriesRange[$seriesNum])) {
-				if ($finalDigit < $seriesRange[$seriesNum]['max']) $seriesRange[$seriesNum]['max'] = $routeNumber;
-				if ($finalDigit > $seriesRange[$seriesNum]['min']) $seriesRange[$seriesNum]['min'] = $routeNumber;
+	if (isset($bynumber)) {
+		$routes = getRoutesByNumber();
+		$routeSeries = Array();
+		$seriesRange = Array();
+		foreach ($routes as $key => $routeNumber) {
+			foreach (explode(" ", $routeNumber['route_short_name']) as $routeNumber) {
+				$seriesNum = substr($routeNumber, 0, -1) . "0";
+				if ($seriesNum == "0") $seriesNum = $routeNumber;
+				$finalDigit = substr($routeNumber, sizeof($routeNumber) - 1, 1);
+				if (isset($seriesRange[$seriesNum])) {
+					if ($finalDigit < $seriesRange[$seriesNum]['max']) $seriesRange[$seriesNum]['max'] = $routeNumber;
+					if ($finalDigit > $seriesRange[$seriesNum]['min']) $seriesRange[$seriesNum]['min'] = $routeNumber;
+				}
+				else {
+					$seriesRange[$seriesNum]['max'] = $routeNumber;
+					$seriesRange[$seriesNum]['min'] = $routeNumber;
+				}
+				$routeSeries[$seriesNum][$seriesNum . "-" . $row[1] . "-" . $row[0]] = $row;
 			}
-			else {
-				$seriesRange[$seriesNum]['max'] = $routeNumber;
-				$seriesRange[$seriesNum]['min'] = $routeNumber;
-			}
-			$routeSeries[$seriesNum][$seriesNum . "-" . $row[1] . "-" . $row[0]] = $row;
 		}
-	}
-	if ($_REQUEST['bynumber']) {
 		ksort($routeSeries);
 		ksort($seriesRange);
 		foreach ($routeSeries as $series => $routes) {
-			echo '<li><a href="' . curPageURL() . 'routeList.php?numberSeries=' . $series . '">';
+			echo '<li><a href="' . curPageURL() . '/routeList.php?numberSeries=' . $series . '">';
 			if ($series <= 9) echo $series;
 			else echo "{$seriesRange[$series]['min']}-{$seriesRange[$series]['max']}";
 			echo "</a></li>\n";
 		}
 	}
-	else if ($_REQUEST['numberSeries']) {
-		foreach ($routeSeries[$_REQUEST['numberSeries']] as $row) {
-			echo '<li>' . $row[1] . ' <a href="trip.php?routeid=' . $row[0] . '">' . $row[2] . " (" . ucwords($row[3]) . ")</a></li>\n";
+	else if ($numberSeries) {
+		$routes = getRoutesByNumber($numberSeries);
+		foreach ($routes as $route) {
+			echo '<li> <a href="trip.php?routeid=' . $route['route_id'] . '"><h3>' . $route['route_short_name'] . "</h3><p>" . $route['route_long_name'] . " (" . ucwords($route['service_id']) . ")</p></a></li>\n";
 		}
 	}
 }
@@ -107,20 +110,14 @@
 	include_header("Routes by Destination", "routeList");
 	navbar();
 	echo ' <ul data-role="listview"  data-inset="true">';
-	$url = $APIurl . "/json/routes";
-	$contents = json_decode(getPage($url));
-	// by destination!
-	foreach ($contents as $row) {
-		$routeDestinations[$row[2]][] = $row;
-	}
-	if ($_REQUEST['routeDestination']) {
-		foreach ($routeDestinations[urldecode($_REQUEST['routeDestination'])] as $row) {
-			echo '<li>' . $row[1] . ' <a href="trip.php?routeid=' . $row[0] . '">' . $row[2] . " (" . ucwords($row[3]) . ")</a></li>\n";
+	if (isset($routeDestination)) {
+		foreach (getRoutesByDestination($routeDestination) as $route) {
+			echo '<li><a href="trip.php?routeid=' . $route["route_id"] . '"><h3>' . $route["route_short_name"] . '</h3><p>' . $route["route_long_name"] . " (" . ucwords($route['service_id']) . ")</p></a></li>\n";
 		}
 	}
 	else {
-		foreach ($routeDestinations as $destination => $routes) {
-			echo '<li><a href="' . curPageURL() . 'routeList.php?routeDestination=' . urlencode($destination) . '">' . $destination . "... </a></li>\n";
+		foreach (getRoutesByDestination() as $destination) {
+			echo '<li><a href="' . curPageURL() . '/routeList.php?routeDestination=' . urlencode($destination['route_long_name']) . '">' . $destination['route_long_name'] . "... </a></li>\n";
 		}
 	}
 }

file:a/schedule_viewer.py (deleted)
--- a/schedule_viewer.py
+++ /dev/null
@@ -1,733 +1,1 @@
-#!/usr/bin/python2.5
 
-# Copyright (C) 2007 Google Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-An example application that uses the transitfeed module.
-
-You must provide a Google Maps API key.
-"""
-
-
-import BaseHTTPServer, sys, urlparse
-import bisect
-from gtfsscheduleviewer.marey_graph import MareyGraph
-import gtfsscheduleviewer
-import mimetypes
-import os.path
-import re
-import signal
-import simplejson
-import socket
-import time
-import datetime
-import transitfeed
-from transitfeed import util
-import urllib
-
-
-# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break
-# raise a KeyboardInterrupt.
-if hasattr(signal, 'SIGBREAK'):
-  signal.signal(signal.SIGBREAK, signal.default_int_handler)
-
-
-mimetypes.add_type('text/plain', '.vbs')
-
-
-class ResultEncoder(simplejson.JSONEncoder):
-  def default(self, obj):
-    try:
-      iterable = iter(obj)
-    except TypeError:
-      pass
-    else:
-      return list(iterable)
-    return simplejson.JSONEncoder.default(self, obj)
-
-# Code taken from
-# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt
-# An alternate approach is shown at
-# http://mail.python.org/pipermail/python-list/2003-July/212751.html
-# but it requires multiple threads. A sqlite object can only be used from one
-# thread.
-class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
-  def server_bind(self):
-    BaseHTTPServer.HTTPServer.server_bind(self)
-    self.socket.settimeout(1)
-    self._run = True
-
-  def get_request(self):
-    while self._run:
-      try:
-        sock, addr = self.socket.accept()
-        sock.settimeout(None)
-        return (sock, addr)
-      except socket.timeout:
-        pass
-
-  def stop(self):
-    self._run = False
-
-  def serve(self):
-    while self._run:
-      self.handle_request()
-
-
-def StopToTuple(stop):
-  """Return tuple as expected by javascript function addStopMarkerFromList"""
-  return (stop.stop_id, stop.stop_name, float(stop.stop_lat),
-          float(stop.stop_lon), stop.location_type, stop.stop_code)
-def StopZoneToTuple(stop):
-  """Return tuple as expected by javascript function addStopMarkerFromList"""
-  return (stop.stop_id, stop.stop_name, float(stop.stop_lat),
-          float(stop.stop_lon), stop.location_type, stop.stop_code, stop.zone_id)
-
-class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
-  cache = {}
-  
-  def do_GET(self):
-    scheme, host, path, x, params, fragment = urlparse.urlparse(self.path)
-    parsed_params = {}
-    for k in params.split('&'):
-      k = urllib.unquote(k)
-      if '=' in k:
-        k, v = k.split('=', 1)
-        parsed_params[k] = unicode(v, 'utf8')
-      else:
-        parsed_params[k] = ''
-
-    if path == '/':
-      return self.handle_GET_home()
-
-    m = re.match(r'/json/([a-z]{1,64})', path)
-    if m:
-      handler_name = 'handle_json_GET_%s' % m.group(1)
-      handler = getattr(self, handler_name, None)
-      if callable(handler):
-        return self.handle_json_wrapper_GET(handler, parsed_params, handler_name)
-
-    # Restrict allowable file names to prevent relative path attacks etc
-    m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path)
-    if m and m.group(1):
-      try:
-        f, mime_type = self.OpenFile(m.group(1))
-        return self.handle_static_file_GET(f, mime_type)
-      except IOError, e:
-        print "Error: unable to open %s" % m.group(1)
-        # Ignore and treat as 404
-
-    m = re.match(r'/([a-z]{1,64})', path)
-    if m:
-      handler_name = 'handle_GET_%s' % m.group(1)
-      handler = getattr(self, handler_name, None)
-      if callable(handler):
-        return handler(parsed_params)
-
-    return self.handle_GET_default(parsed_params, path)
-
-  def OpenFile(self, filename):
-    """Try to open filename in the static files directory of this server.
-    Return a tuple (file object, string mime_type) or raise an exception."""
-    (mime_type, encoding) = mimetypes.guess_type(filename)
-    assert mime_type
-    # A crude guess of when we should use binary mode. Without it non-unix
-    # platforms may corrupt binary files.
-    if mime_type.startswith('text/'):
-      mode = 'r'
-    else:
-      mode = 'rb'
-    return open(os.path.join(self.server.file_dir, filename), mode), mime_type
-
-  def handle_GET_default(self, parsed_params, path):
-    self.send_error(404)
-
-  def handle_static_file_GET(self, fh, mime_type):
-    content = fh.read()
-    self.send_response(200)
-    self.send_header('Content-Type', mime_type)
-    self.send_header('Content-Length', str(len(content)))
-    self.end_headers()
-    self.wfile.write(content)
-
-  def AllowEditMode(self):
-    return False
-
-  def handle_GET_home(self):
-    schedule = self.server.schedule
-    (min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox()
-    forbid_editing = ('true', 'false')[self.AllowEditMode()]
-
-    agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8')
-
-    key = self.server.key
-    host = self.server.host
-
-    # A very simple template system. For a fixed set of values replace [xxx]
-    # with the value of local variable xxx
-    f, _ = self.OpenFile('index.html')
-    content = f.read()
-    for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key',
-              'host', 'forbid_editing'):
-      content = content.replace('[%s]' % v, str(locals()[v]))
-
-    self.send_response(200)
-    self.send_header('Content-Type', 'text/html')
-    self.send_header('Content-Length', str(len(content)))
-    self.end_headers()
-    self.wfile.write(content)
-
-  def handle_json_GET_routepatterns(self, params):
-    """Given a route_id generate a list of patterns of the route. For each
-    pattern include some basic information and a few sample trips."""
-    schedule = self.server.schedule
-    route = schedule.GetRoute(params.get('route', None))
-    if not route:
-      self.send_error(404)
-      return
-    time = int(params.get('time', 0))
-    sample_size = 10  # For each pattern return the start time for this many trips
-
-    pattern_id_trip_dict = route.GetPatternIdTripDict()
-    patterns = []
-
-    for pattern_id, trips in pattern_id_trip_dict.items():
-      time_stops = trips[0].GetTimeStops()
-      if not time_stops:
-        continue
-      has_non_zero_trip_type = False;
-      for trip in trips:
-        if trip['trip_type'] and trip['trip_type'] != '0':
-          has_non_zero_trip_type = True
-      name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops))
-      transitfeed.SortListOfTripByTime(trips)
-
-      num_trips = len(trips)
-      if num_trips <= sample_size:
-        start_sample_index = 0
-        num_after_sample = 0
-      else:
-        # Will return sample_size trips that start after the 'time' param.
-
-        # Linear search because I couldn't find a built-in way to do a binary
-        # search with a custom key.
-        start_sample_index = len(trips)
-        for i, trip in enumerate(trips):
-          if trip.GetStartTime() >= time:
-            start_sample_index = i
-            break
-
-        num_after_sample = num_trips - (start_sample_index + sample_size)
-        if num_after_sample < 0:
-          # Less than sample_size trips start after 'time' so return all the
-          # last sample_size trips.
-          num_after_sample = 0
-          start_sample_index = num_trips - sample_size
-
-      sample = []
-      for t in trips[start_sample_index:start_sample_index + sample_size]:
-        sample.append( (t.GetStartTime(), t.trip_id) )
-
-      patterns.append((name, pattern_id, start_sample_index, sample,
-                       num_after_sample, (0,1)[has_non_zero_trip_type]))
-
-    patterns.sort()
-    return patterns
-
-  def handle_json_wrapper_GET(self, handler, parsed_params, handler_name):
-    """Call handler and output the return value in JSON."""
-    schedule = self.server.schedule
-    # round times to nearest 100 seconds
-    if "time" in parsed_params:
-      parsed_params['time'] = int(round(float(parsed_params['time']),-2))
-    paramkey = tuple(sorted(parsed_params.items()))
-    if handler_name in self.cache and paramkey in self.cache[handler_name] :
-      print ("Cache hit for ",handler_name," params ",parsed_params)
-    else:
-      print ("Cache miss for ",handler_name," params ",parsed_params)
-      result = handler(parsed_params)
-      if not handler_name in self.cache:
-        self.cache[handler_name] = {}
-      self.cache[handler_name][paramkey] = ResultEncoder().encode(result)
-    content = self.cache[handler_name][paramkey]
-    self.send_response(200)
-    self.send_header('Content-Type', 'text/plain')
-    self.send_header('Content-Length', str(len(content)))
-    self.end_headers()
-    self.wfile.write(content)
-
-  def handle_json_GET_routes(self, params):
-    """Return a list of all routes."""
-    schedule = self.server.schedule
-    result = []
-    for r in schedule.GetRouteList():
-      servicep = None
-      for t in schedule.GetTripList():
-        if t.route_id == r.route_id:
-          servicep = t.service_period
-          break
-      result.append( (r.route_id, r.route_short_name, r.route_long_name, servicep.service_id) )
-    result.sort(key = lambda x: x[1:3])
-    return result
-
-  def handle_json_GET_routesearch(self, params):
-    """Return a list of routes with matching short name."""
-    schedule = self.server.schedule
-    routeshortname = params.get('routeshortname', None)
-    result = []
-    for r in schedule.GetRouteList():
-      if r.route_short_name == routeshortname:
-        servicep = None
-        for t in schedule.GetTripList():
-          if t.route_id == r.route_id:
-            servicep = t.service_period
-            break
-        result.append( (r.route_id, r.route_short_name, r.route_long_name, servicep.service_id) )
-    result.sort(key = lambda x: x[1:3])
-    return result
-
-
-  def handle_json_GET_routerow(self, params):
-    schedule = self.server.schedule
-    route = schedule.GetRoute(params.get('route', None))
-    return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()]
-  
-  def handle_json_GET_routetrips(self, params):
-    """ Get a trip for a route_id (preferablly the next one) """
-    schedule = self.server.schedule
-    query = params.get('route_id', None).lower()
-    result = []
-    for t in schedule.GetTripList():
-      if t.route_id == query:
-        try:
-          starttime = t.GetStartTime()  
-        except:
-          print "Error for GetStartTime of trip #" + t.trip_id + sys.exc_info()[0]
-        else:
-          cursor = t._schedule._connection.cursor()
-          cursor.execute(
-              'SELECT arrival_secs,departure_secs FROM stop_times WHERE '
-              'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (t.trip_id,))
-          (arrival_secs, departure_secs) = cursor.fetchone()
-          if arrival_secs != None:
-            endtime = arrival_secs
-          elif departure_secs != None:
-            endtime = departure_secs
-          else:
-            endtime =0
-          result.append ( (starttime, t.trip_id, endtime) )
-    return sorted(result, key=lambda trip: trip[2])
-  
-  def handle_json_GET_triprows(self, params):
-    """Return a list of rows from the feed file that are related to this
-    trip."""
-    schedule = self.server.schedule
-    try:
-      trip = schedule.GetTrip(params.get('trip', None))
-    except KeyError:
-      # if a non-existent trip is searched for, the return nothing
-      return
-    route = schedule.GetRoute(trip.route_id)
-    trip_row = dict(trip.iteritems())
-    route_row = dict(route.iteritems())
-    return [['trips.txt', trip_row], ['routes.txt', route_row]]
-
-  def handle_json_GET_tripstoptimes(self, params):
-    schedule = self.server.schedule
-    try:
-      trip = schedule.GetTrip(params.get('trip'))
-    except KeyError:
-       # if a non-existent trip is searched for, the return nothing
-      return
-    time_stops = trip.GetTimeInterpolatedStops()
-    stops = []
-    times = []
-    for arr,ts,is_timingpoint in time_stops:
-      stops.append(StopToTuple(ts.stop))
-      times.append(arr)
-    return [stops, times]
-
-  def handle_json_GET_tripshape(self, params):
-    schedule = self.server.schedule
-    try:
-      trip = schedule.GetTrip(params.get('trip'))
-    except KeyError:
-       # if a non-existent trip is searched for, the return nothing
-      return
-    points = []
-    if trip.shape_id:
-      shape = schedule.GetShape(trip.shape_id)
-      for (lat, lon, dist) in shape.points:
-        points.append((lat, lon))
-    else:
-      time_stops = trip.GetTimeStops()
-      for arr,dep,stop in time_stops:
-        points.append((stop.stop_lat, stop.stop_lon))
-    return points
-
-#
-# GeoPo Encode in Python
-# @author : Shintaro Inagaki
-# @param location (Dictionary) [lat (Float), lng (Float), scale(Int)]
-# @return geopo (String)
-#
-     
-  def handle_json_GET_neareststops(self, params):
-    """Return a list of the nearest 'limit' stops to 'lat', 'lon'"""
-    schedule = self.server.schedule
-    lat = float(params.get('lat'))
-    lon = float(params.get('lon'))
-    limit = int(params.get('limit',5))
-    scale = int(params.get('scale',5)) # 5 = neighbourhood ~ 1km, 4= town 5 by 7km
-    stops = []
-    
-    # 64characters (number + big and small letter + hyphen + underscore)
-    chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
-
-    geopo = ""
-
-    # Change a degree measure to a decimal number
-    lat = (lat + 90.0) / 180 * 8 ** 10 # 90.0 is forced FLOAT type when lat is INT
-    lon = (lon + 180.0) / 360 * 8 ** 10 # 180.0 is same
-
-       # Compute a GeoPo code from head and concatenate
-    for i in range(scale):
-            order = int(lat / (8 ** (9 - i)) % 8) + int(lon / (8 ** (9 - i)) % 8) * 8
-            geopo = geopo + chars[order]
-
-    
-    for s in schedule.GetStopList():
-      if s.stop_code.find(geopo) != -1:
-        stops.append(s)
-        
-    if scale == 5:
-      print stops
-      return [StopToTuple(s) for s in stops]
-    else:
-      dist_stop_list = []
-      for s in stops:
-      # TODO: Use util.ApproximateDistanceBetweenStops?
-        dist = (s.stop_lat - lat)**2 + (s.stop_lon - lon)**2
-        if len(dist_stop_list) < limit:
-          bisect.insort(dist_stop_list, (dist, s))
-        elif dist < dist_stop_list[-1][0]:
-          bisect.insort(dist_stop_list, (dist, s))
-          dist_stop_list.pop()  # Remove stop with greatest distance
-      print dist_stop_list
-      return [StopToTuple(s) for dist, s in dist_stop_list]
-
-  def handle_json_GET_boundboxstops(self, params):
-    """Return a list of up to 'limit' stops within bounding box with 'n','e'
-    and 's','w' in the NE and SW corners. Does not handle boxes crossing
-    longitude line 180."""
-    schedule = self.server.schedule
-    n = float(params.get('n'))
-    e = float(params.get('e'))
-    s = float(params.get('s'))
-    w = float(params.get('w'))
-    limit = int(params.get('limit'))
-    stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit)
-    return [StopToTuple(s) for s in stops]
-
-  def handle_json_GET_stops(self, params):
-    schedule = self.server.schedule
-    return [StopToTuple(s) for s in schedule.GetStopList()]
-    
-  def handle_json_GET_timingpoints(self, params):
-    schedule = self.server.schedule
-    matches = []
-    for s in schedule.GetStopList():
-      if s.stop_code.find("Wj") == -1:
-        matches.append(StopToTuple(s))
-    return matches
-
-  def handle_json_GET_stopsearch(self, params):
-    schedule = self.server.schedule
-    query = params.get('q', None).lower()
-    matches = []
-    for s in schedule.GetStopList():
-      if s.stop_name.lower().find(query) != -1 or s.stop_code.lower().find(query) != -1:
-        matches.append(StopToTuple(s))
-    return matches
-
-  def handle_json_GET_stopnamesearch(self, params):
-    schedule = self.server.schedule
-    query = params.get('q', None).lower()
-    matches = []
-    for s in schedule.GetStopList():
-      if s.stop_name.lower().find(query) != -1:
-        matches.append(StopToTuple(s))
-    return matches
-  
-  def handle_json_GET_stopcodesearch(self, params):
-    schedule = self.server.schedule
-    query = params.get('q', None).lower()
-    matches = []
-    for s in schedule.GetStopList():
-      if s.stop_code.lower().find(query) != -1:
-        matches.append(StopToTuple(s))
-    return matches
-
-  def handle_json_GET_stopzonesearch(self, params):
-    schedule = self.server.schedule
-    query = params.get('q', None).lower()
-    matches = []
-    for s in schedule.GetStopList():
-      if s.zone_id != None and s.zone_id.lower().find(query) != -1:
-        matches.append(StopToTuple(s))
-    return matches
-
-  def handle_json_GET_stop(self, params):
-    schedule = self.server.schedule
-    query = params.get('stop_id', None).lower()
-    for s in schedule.GetStopList():
-      if s.stop_id.lower() == query:
-        return StopToTuple(s)
-    return []
-  def handle_json_GET_stoproutes(self, params):
-    """Given a stop_id return all routes to visit the stop."""
-    schedule = self.server.schedule
-    stop = schedule.GetStop(params.get('stop', None))
-    service_period = params.get('service_period', None)
-    trips = stop.GetTrips(schedule)
-    result = {}
-    for trip in trips:
-      route = schedule.GetRoute(trip.route_id)
-      if not route.route_short_name+route.route_long_name+trip.service_id in result:
-        result[route.route_short_name+route.route_long_name+trip.service_id] = (route.route_id, route.route_short_name, route.route_long_name, trip.trip_id, trip.service_id)
-    return result
-    
-  def handle_json_GET_stopalltrips(self, params):
-    """Given a stop_id return all trips to visit the stop."""
-    schedule = self.server.schedule
-    stop = schedule.GetStop(params.get('stop', None))
-    service_period = params.get('service_period', None)
-    time_trips = stop.GetStopTimeTrips(schedule)
-    result = []
-    for time, (trip, index), tp in time_trips:
-      headsign = None
-      # Find the most recent headsign from the StopTime objects
-      for stoptime in trip.GetStopTimes()[index::-1]:
-        if stoptime.stop_headsign:
-          headsign = stoptime.stop_headsign
-          break
-      # If stop_headsign isn't found, look for a trip_headsign
-      if not headsign:
-        headsign = trip.trip_headsign
-      route = schedule.GetRoute(trip.route_id)
-      trip_name = ''
-      if route.route_short_name:
-        trip_name += route.route_short_name
-      if route.route_long_name:
-        if len(trip_name):
-          trip_name += " - "
-        trip_name += route.route_long_name
-      if service_period == None or trip.service_id == service_period:
-        result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))
-    return result
-  
-
-
-  def handle_json_GET_stoptrips(self, params):
-    """Given a stop_id and time in seconds since midnight return the next
-    trips to visit the stop."""
-    schedule = self.server.schedule
-    stop = schedule.GetStop(params.get('stop', None))
-    requested_time = int(params.get('time', 0))
-    limit = int(params.get('limit', 15))
-    service_period = params.get('service_period', None)
-    time_range = int(params.get('time_range', 24*60*60))
-    
-    
-    filtered_time_trips = []
-    for trip, index in stop._GetTripIndex(schedule):
-      tripstarttime = trip.GetStartTime()
-      if tripstarttime > requested_time and tripstarttime < (requested_time + time_range):
-        time, stoptime, tp = trip.GetTimeInterpolatedStops()[index]
-        if time > requested_time and time < (requested_time + time_range):
-          bisect.insort(filtered_time_trips, (time, (trip, index), tp))
-    
-    result = []
-    for time, (trip, index), tp in filtered_time_trips:
-      if len(result) > limit:
-        break
-      headsign = None
-      # Find the most recent headsign from the StopTime objects
-      for stoptime in trip.GetStopTimes()[index::-1]:
-        if stoptime.stop_headsign:
-          headsign = stoptime.stop_headsign
-          break
-      # If stop_headsign isn't found, look for a trip_headsign
-      if not headsign:
-        headsign = trip.trip_headsign
-      route = schedule.GetRoute(trip.route_id)
-      trip_name = ''
-      if route.route_short_name:
-        trip_name += route.route_short_name
-      if route.route_long_name:
-        if len(trip_name):
-          trip_name += " - "
-        trip_name += route.route_long_name
-        # comment out directions because we already have them in the long name
-      #if headsign:
-      #  trip_name += " (Direction: %s)" % headsign
-      if service_period == None or trip.service_id == service_period:
-        result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))
-    return result
-
-  def handle_GET_ttablegraph(self,params):
-    """Draw a Marey graph in SVG for a pattern (collection of trips in a route
-    that visit the same sequence of stops)."""
-    schedule = self.server.schedule
-    marey = MareyGraph()
-    trip = schedule.GetTrip(params.get('trip', None))
-    route = schedule.GetRoute(trip.route_id)
-    height = int(params.get('height', 300))
-
-    if not route:
-      print 'no such route'
-      self.send_error(404)
-      return
-
-    pattern_id_trip_dict = route.GetPatternIdTripDict()
-    pattern_id = trip.pattern_id
-    if pattern_id not in pattern_id_trip_dict:
-      print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys())
-      self.send_error(404)
-      return
-    triplist = pattern_id_trip_dict[pattern_id]
-
-    pattern_start_time = min((t.GetStartTime() for t in triplist))
-    pattern_end_time = max((t.GetEndTime() for t in triplist))
-
-    marey.SetSpan(pattern_start_time,pattern_end_time)
-    marey.Draw(triplist[0].GetPattern(), triplist, height)
-
-    content = marey.Draw()
-
-    self.send_response(200)
-    self.send_header('Content-Type', 'image/svg+xml')
-    self.send_header('Content-Length', str(len(content)))
-    self.end_headers()
-    self.wfile.write(content)
-
-
-def FindPy2ExeBase():
-  """If this is running in py2exe return the install directory else return
-  None"""
-  # py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is
-  # configured to put the data next to library.zip.
-  windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\')
-  if windows_ending != -1:
-    return transitfeed.__file__[:windows_ending]
-  else:
-    return None
-
-
-def FindDefaultFileDir():
-  """Return the path of the directory containing the static files. By default
-  the directory is called 'files'. The location depends on where setup.py put
-  it."""
-  base = FindPy2ExeBase()
-  if base:
-    return os.path.join(base, 'schedule_viewer_files')
-  else:
-    # For all other distributions 'files' is in the gtfsscheduleviewer
-    # directory.
-    base = os.path.dirname(gtfsscheduleviewer.__file__)  # Strip __init__.py
-    return os.path.join(base, 'files')
-
-
-def GetDefaultKeyFilePath():
-  """In py2exe return absolute path of file in the base directory and in all
-  other distributions return relative path 'key.txt'"""
-  windows_base = FindPy2ExeBase()
-  if windows_base:
-    return os.path.join(windows_base, 'key.txt')
-  else:
-    return 'key.txt'
-
-
-def main(RequestHandlerClass = ScheduleRequestHandler):
-  usage = \
-'''%prog [options] [<input GTFS.zip>]
-
-Runs a webserver that lets you explore a <input GTFS.zip> in your browser.
-
-If <input GTFS.zip> is omited the filename is read from the console. Dragging
-a file into the console may enter the filename.
-'''
-  parser = util.OptionParserLongError(
-      usage=usage, version='%prog '+transitfeed.__version__)
-  parser.add_option('--feed_filename', '--feed', dest='feed_filename',
-                    help='file name of feed to load')
-  parser.add_option('--key', dest='key',
-                    help='Google Maps API key or the name '
-                    'of a text file that contains an API key')
-  parser.add_option('--host', dest='host', help='Host name of Google Maps')
-  parser.add_option('--port', dest='port', type='int',
-                    help='port on which to listen')
-  parser.add_option('--file_dir', dest='file_dir',
-                    help='directory containing static files')
-  parser.add_option('-n', '--noprompt', action='store_false',
-                    dest='manual_entry',
-                    help='disable interactive prompts')
-  parser.set_defaults(port=8765,
-                      host='maps.google.com',
-                      file_dir=FindDefaultFileDir(),
-                      manual_entry=True)
-  (options, args) = parser.parse_args()
-
-  if not os.path.isfile(os.path.join(options.file_dir, 'index.html')):
-    print "Can't find index.html with --file_dir=%s" % options.file_dir
-    exit(1)
-
-  if not options.feed_filename and len(args) == 1:
-    options.feed_filename = args[0]
-
-  if not options.feed_filename and options.manual_entry:
-    options.feed_filename = raw_input('Enter Feed Location: ').strip('"')
-
-  default_key_file = GetDefaultKeyFilePath()
-  if not options.key and os.path.isfile(default_key_file):
-    options.key = open(default_key_file).read().strip()
-
-  if options.key and os.path.isfile(options.key):
-    options.key = open(options.key).read().strip()
-  
-  schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter())
-  print 'Loading data from feed "%s"...' % options.feed_filename
-  print '(this may take a few minutes for larger cities)'
-  t0 = datetime.datetime.now()
-  schedule.Load(options.feed_filename)
-  print ("Loaded in", (datetime.datetime.now() - t0).seconds , "seconds")
-  server = StoppableHTTPServer(server_address=('', options.port),
-                               RequestHandlerClass=RequestHandlerClass)
-  server.key = options.key
-  server.schedule = schedule
-  server.file_dir = options.file_dir
-  server.host = options.host
-  server.feed_path = options.feed_filename
-  
-
-
-  print ("To view, point your browser at http://localhost:%d/" %
-         (server.server_port))
-  server.serve_forever()
-
-
-if __name__ == '__main__':
-  main()
-

file:a/stop.php -> file:b/stop.php
--- a/stop.php
+++ b/stop.php
@@ -1,10 +1,7 @@
 <?php
 include ('include/common.inc.php');
-$stopid = filter_var($_REQUEST['stopid'], FILTER_SANITIZE_NUMBER_INT);
-$stopcode = filter_var($_REQUEST['stopcode'], FILTER_SANITIZE_STRING);
-$url = $APIurl . "/json/stop?stop_id=" . $stopid;
-$stop = json_decode(getPage($url));
-if ($stopcode != "" && $stop[5] != $stopcode) {
+if ($stopid) $stop = getStop($stopid);
+/*if ($stopcode != "" && $stop[5] != $stopcode) {
 	$url = $APIurl . "/json/stopcodesearch?q=" . $stopcode;
 	$stopsearch = json_decode(getPage($url));
 	$stopid = $stopsearch[0][0];
@@ -14,90 +11,102 @@
 if (!startsWith($stop[5], "Wj") && strpos($stop[1], "Platform") === false) {
 	// expand out to all platforms
 	
-}
+}*/
 $stops = Array();
 $stopPositions = Array();
 $stopNames = Array();
 $tripStopNumbers = Array();
 $allStopsTrips = Array();
+$fetchedTripSequences = Array();
 $stopLinks = "";
-if (isset($_REQUEST['stopids'])) {
-	$stopids = explode(",", filter_var($_REQUEST['stopids'], FILTER_SANITIZE_STRING));
+if (isset($stopids)) {
 	foreach ($stopids as $sub_stopid) {
-		$url = $APIurl . "/json/stop?stop_id=" . $sub_stopid;
-		$stop = json_decode(getPage($url));
-		$stops[] = $stop;
+		$stops[] = getStop($sub_stopid);
 	}
 	$stop = $stops[0];
-	$stopid = $stops[0][0];
+	$stopid = $stops[0]["stop_id"];
 	$stopLinks.= "Individual stop pages: ";
 	foreach ($stops as $key => $sub_stop) {
-	//	$stopNames[$key] = $sub_stop[1] . ' Stop #' . ($key + 1);
-        if (strpos($stop[1],
-                   "Station")) {
-		$stopNames[$key] = 'Platform ' . ($key + 1);
-		$stopLinks.= '<a href="stop.php?stopid=' . $sub_stop[0] . '&stopcode=' . $sub_stop[5] . '">' . $sub_stop[1] . '</a> ';  
-        }         else {
-		$stopNames[$key] = '#' . ($key + 1);
-		$stopLinks.= '<a href="stop.php?stopid=' . $sub_stop[0] . '&stopcode=' . $sub_stop[5] . '">' . $sub_stop[1] . ' Stop #' . ($key + 1) . '</a> ';
-        }
+		//	$stopNames[$key] = $sub_stop[1] . ' Stop #' . ($key + 1);
+		if (strpos($stop["stop_name"], "Station")) {
+			$stopNames[$key] = 'Platform ' . ($key + 1);
+			$stopLinks.= '<a href="stop.php?stopid=' . $sub_stop["stop_id"] . '&amp;stopcode=' . $sub_stop["stop_code"] . '">' . $sub_stop["stop_name"] . '</a> ';
+		}
+		else {
+			$stopNames[$key] = '#' . ($key + 1);
+			$stopLinks.= '<a href="stop.php?stopid=' . $sub_stop["stop_id"] . '&amp;stopcode=' . $sub_stop["stop_code"] . '">' . $sub_stop["stop_name"] . ' Stop #' . ($key + 1) . '</a> ';
+		}
 		$stopPositions[$key] = Array(
-			$sub_stop[2],
-			$sub_stop[3]
+			$sub_stop["stop_lat"],
+			$sub_stop["stop_lon"]
 		);
-		$url = $APIurl . "/json/stoptrips?stop=" . $sub_stop[0] . "&time=" . midnight_seconds() . "&service_period=" . service_period();
-		$trips = json_decode(getPage($url));
+		$trips = getStopTrips($sub_stop["stop_id"]);
+		$tripSequence = "";
 		foreach ($trips as $trip) {
-			if (!isset($allStopsTrips[$trip[1][0]])) $allStopsTrips[$trip[1][0]] = $trip;
-			$tripStopNumbers[$trip[1][0]][] = $key;
+			$tripSequence.= "{$trip['trip_id']},";
+			$tripStopNumbers[$trip['trip_id']][] = $key;
 		}
+		if (!in_array($tripSequence, $fetchedTripSequences)) {
+			// only fetch new trip sequences
+			$fetchedTripSequences[] = $tripSequence;
+			$trips = getStopTripsWithTimes($sub_stop["stop_id"]);
+			foreach ($trips as $trip) {
+				if (!isset($allStopsTrips[$trip["trip_id"]])) $allStopsTrips[$trip["trip_id"]] = $trip;
+			}
+		}
+		//else {
+		//	echo "skipped sequence $tripSequence";
+		//}
 	}
 }
-include_header($stop[1], "stop");
+include_header($stop['stop_name'], "stop");
 timePlaceSettings();
-echo '<div data-role="content" class="ui-content" role="main">        <a name="maincontent" id="maincontent"></a>';
 echo $stopLinks;
 if (sizeof($stops) > 0) {
-    trackEvent("View Stops","View Combined Stops", $stop[1], $stop[0]);
-
-	echo '<p>' . staticmap($stopPositions) . '</p>';
+	trackEvent("View Stops", "View Combined Stops", $stop["stop_name"], $stop["stop_id"]);
+	echo staticmap($stopPositions);
 }
 else {
-        trackEvent("View Stops","View Single Stop", $stop[1], $stop[0]);
-	echo '<p>' . staticmap(Array(
+	trackEvent("View Stops", "View Single Stop", $stop["stop_name"], $stop["stop_id"]);
+	echo staticmap(Array(
 		0 => Array(
-			$stop[2],
-			$stop[3]
+			$stop["stop_lat"],
+			$stop["stop_lon"]
 		)
-	)) . '</p>';
+	)) ;
 }
 echo '  <ul data-role="listview"  data-inset="true">';
 if (sizeof($allStopsTrips) > 0) {
-    sksort($allStopsTrips,0, $true);
+    sktimesort($allStopsTrips,"arrival_time", true);
 	$trips = $allStopsTrips;
 }
 else {
-	$url = $APIurl . "/json/stoptrips?stop=" . $stopid . "&time=" . midnight_seconds() . "&service_period=" . service_period();
-	$trips = json_decode(getPage($url));
+	$trips = getStopTripsWithTimes($stopid);
 }
-foreach ($trips as $row) {
-	echo '<li>';
-	echo '<h3><a href="trip.php?stopid=' . $stopid . '&tripid=' . $row[1][0] . '">' . $row[1][1];
-        $viaPoints = viaPointNames($row[1][0], $stopid);
-        if ($viaPoints != "") echo '<br><span class="viaPoints">Via: ' . $viaPoints . '</span>';
-	if (sizeof($tripStopNumbers) > 0) {
-            echo '<br><small>Boarding At: ';
-            foreach ($tripStopNumbers[$row[1][0]] as $key) {
-                echo $stopNames[$key] .' ';
-            }
-            echo '</small>';
-        }
-	echo '</a></h3>';
-	echo '<p class="ui-li-aside"><strong>' . midnight_seconds_to_time($row[0]) . '</strong></p>';
-	echo '</li>';
+if (sizeof($trips) == 0) {
+	echo "<li style='text-align: center;'>No trips in the near future.</li>";
 }
-if (sizeof($trips) == 0) echo "<li> <center>No trips in the near future.</center> </li>";
-echo '</ul></div>';
+else {
+	foreach ($trips as $trip) {
+		echo '<li>';
+		echo '<a href="trip.php?stopid=' . $stopid . '&amp;tripid=' . $trip['trip_id'] . '"><h3>' . $trip['route_short_name'] . " " . $trip['route_long_name'] . "</h3><p>";
+		$viaPoints = viaPointNames($trip['trip_id'], $trip['stop_sequence']);
+		if ($viaPoints != "") echo '<br><span class="viaPoints">Via: ' . $viaPoints . '</span>';
+		if (sizeof($tripStopNumbers) > 0) {
+			echo '<br><small>Boarding At: ';
+			foreach ($tripStopNumbers[$trip['trip_id']] as $key) {
+				echo $stopNames[$key] . ' ';
+			}
+			echo '</small>';
+		}
+		echo '</p>';
+		echo '<p class="ui-li-aside"><strong>' . $trip['arrival_time'] . '</strong></p>';
+		echo '</a></li>';
+		flush();
+		@ob_flush();
+	}
+}
+echo '</ul>';
 include_footer();
 ?>
 

--- a/stopList.php
+++ b/stopList.php
@@ -1,16 +1,13 @@
 <?php
 include ('include/common.inc.php');
-function filterByFirstLetter($var)
-{
-	return $var[1][0] == $_REQUEST['firstLetter'];
-}
+$stops = Array();
 function navbar()
 {
 	echo '
 		<div data-role="navbar">
 			<ul> 
 				<li><a href="stopList.php">Timing Points</a></li>
-				<li><a href="stopList.php?suburbs=yes">By Suburb</a></li>
+				<li><a href="stopList.php?bysuburbs=yes">By Suburb</a></li>
 				<li><a href="stopList.php?nearby=yes">Nearby Stops</a></li>
 				<li><a href="stopList.php?allstops=yes">All Stops</a></li> 
 			</ul>
@@ -18,18 +15,18 @@
 	';
 }
 // By suburb
-if (isset($_REQUEST['suburbs'])) {
+if (isset($bysuburbs)) {
 	include_header("Stops by Suburb", "stopList");
 	navbar();
 	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
-	if (!isset($_REQUEST['firstLetter'])) {
+	if (!isset($firstLetter)) {
 		foreach (range('A', 'Z') as $letter) {
-			echo "<li><a href=\"stopList.php?firstLetter=$letter&suburbs=yes\">$letter...</a></li>\n";
+			echo "<li><a href=\"stopList.php?firstLetter=$letter&amp;bysuburbs=yes\">$letter...</a></li>\n";
 		}
 	}
 	else {
 		foreach ($suburbs as $suburb) {
-			if (startsWith($suburb, $_REQUEST['firstLetter'])) {
+			if (startsWith($suburb, $firstLetter)) {
 				echo '<li><a href="stopList.php?suburb=' . urlencode($suburb) . '">' . $suburb . '</a></li>';
 			}
 		}
@@ -38,100 +35,95 @@
 }
 else {
 	// Timing Points / All stops
-	if ($_REQUEST['allstops']) {
+	if (isset($allstops)) {
 		$listType = 'allstops=yes';
-		$url = $APIurl . "/json/stops";
+		$stops = getStops();
 		include_header("All Stops", "stopList");
 		navbar();
 		timePlaceSettings();
 	}
-	else if ($_REQUEST['nearby']) {
+	else if (isset($nearby)) {
 		$listType = 'nearby=yes';
-		$url = $APIurl . "/json/neareststops?lat={$_SESSION['lat']}&lon={$_SESSION['lon']}&limit=15";
 		include_header("Nearby Stops", "stopList", true, true);
+		trackEvent("Stop Lists", "Stops Nearby", $_SESSION['lat'] . "," . $_SESSION['lon']);
 		navbar();
 		timePlaceSettings(true);
 		if (!isset($_SESSION['lat']) || !isset($_SESSION['lat']) || $_SESSION['lat'] == "" || $_SESSION['lon'] == "") {
 			include_footer();
 			die();
 		}
+		$stops = getNearbyStops($_SESSION['lat'], $_SESSION['lon'], 15);
 	}
-	else if ($_REQUEST['suburb']) {
-		$suburb = filter_var($_REQUEST['suburb'], FILTER_SANITIZE_STRING);
-		$listType = "suburb=$suburb";
-		$url = $APIurl . "/json/stopzonesearch?q=" . $suburb;
+	else if (isset($suburb)) {
+		$stops = getStopsBySuburb($suburb);
 		include_header("Stops in " . ucwords($suburb) , "stopList");
 		navbar();
-	       trackEvent("Stop Lists","Stops By Suburb", $suburb);
+		trackEvent("Stop Lists", "Stops By Suburb", $suburb);
 	}
 	else {
-		$url = $APIurl . "/json/timingpoints";
+		$stops = getStops(true, $firstLetter);
 		include_header("Timing Points / Major Stops", "stopList");
 		navbar();
 		timePlaceSettings();
 	}
 	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
-	if (!isset($_REQUEST['firstLetter']) && !$_REQUEST['suburb'] && !$_REQUEST['nearby']) {
+	if (!isset($firstLetter) && !isset($suburb) && !isset($nearby)) {
 		foreach (range('A', 'Z') as $letter) {
-			echo "<li><a href=\"stopList.php?firstLetter=$letter&$listType\">$letter...</a></li>\n";
+			echo "<li><a href=\"stopList.php?firstLetter=$letter&amp;$listType\">$letter...</a></li>\n";
 		}
 	}
 	else {
-		$stops = json_decode(getPage($url));
-		foreach ($stops as $key => $row) {
-			$stopName[$key] = $row[1];
-		}
-		// Sort the stops by name
-		array_multisort($stopName, SORT_ASC, $stops);
-		if (!isset($_REQUEST['suburb']) && !isset($_REQUEST['nearby'])) {
-			$stops = array_filter($stops, "filterByFirstLetter");
-		}
+		//var_dump($stops);
 		$stopsGrouped = Array();
-		foreach ($stops as $key => $row) {
-			if ((trim(preg_replace("/\(Platform.*/", "", $stops[$key][1])) != trim(preg_replace("/\(Platform.*/", "", $stops[$key + 1][1]))) || $key + 1 >= sizeof($stops)) {
+		foreach ($stops as $key => $stop) {
+			if ((trim(preg_replace("/\(Platform.*/", "", $stops[$key]["stop_name"])) != trim(preg_replace("/\(Platform.*/", "", $stops[$key + 1]["stop_name"]))) || $key + 1 >= sizeof($stops)) {
 				if (sizeof($stopsGrouped) > 0) {
 					// print and empty grouped stops
 					// subsequent duplicates
-					$stopsGrouped["stop_ids"][] = $row[0];
+					$stopsGrouped["stop_ids"][] = $stop['stop_id'];
 					echo '<li>';
 					if (!startsWith($stopsGrouped['stop_codes'][0], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point: " class="ui-li-icon">';
 					echo '<a href="stop.php?stopids=' . implode(",", $stopsGrouped['stop_ids']) . '">';
 					if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
-						echo '<span class="ui-li-count">' . distance($row[2], $row[3], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
+						echo '<span class="ui-li-count">' . distance($stop['stop_lat'], $stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
 					}
-					echo bracketsMeanNewLine(trim(preg_replace("/\(Platform.*/", "", $row[1])) . '(' . sizeof($stopsGrouped["stop_ids"]) . ' stops)');
+					echo bracketsMeanNewLine(trim(preg_replace("/\(Platform.*/", "", $stop['stop_name'])) . '(' . sizeof($stopsGrouped["stop_ids"]) . ' stops)');
 					echo "</a></li>\n";
+					flush();
+					@ob_flush();
 					$stopsGrouped = Array();
 				}
 				else {
 					// just a normal stop
 					echo '<li>';
-					if (!startsWith($row[5], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point" class="ui-li-icon">';
-					echo '<a href="stop.php?stopid=' . $row[0] . (startsWith($row[5], "Wj") ? '&stopcode=' . $row[5] : "") . '">';
+					if (!startsWith($stop['stop_code'], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point" class="ui-li-icon">';
+					echo '<a href="stop.php?stopid=' . $stop['stop_id'] . (startsWith($stop['stop_code'], "Wj") ? '&amp;stopcode=' . $stop['stop_code'] : "") . '">';
 					if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
-						echo '<span class="ui-li-count">' . distance($row[2], $row[3], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
+						echo '<span class="ui-li-count">' . distance($stop['stop_lat'], $stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
 					}
-					echo bracketsMeanNewLine($row[1]);
+					echo bracketsMeanNewLine($stop['stop_name']);
 					echo "</a></li>\n";
+					flush();
+					@ob_flush();
 				}
 			}
 			else {
 				// this is a duplicated line item
-				if ($key - 1 <= 0 || (trim(preg_replace("/\(Platform.*/", "", $stops[$key][1])) != trim(preg_replace("/\(Platform.*/", "", $stops[$key - 1][1])))) {
+				if ($key - 1 <= 0 || (trim(preg_replace("/\(Platform.*/", "", $stops[$key]['stop_name'])) != trim(preg_replace("/\(Platform.*/", "", $stops[$key - 1]['stop_name'])))) {
 					// first duplicate
 					$stopsGrouped = Array(
-						"name" => trim(preg_replace("/\(Platform.*/", "", $row[1])) ,
+						"name" => trim(preg_replace("/\(Platform.*/", "", $stop['stop_name'])) ,
 						"stop_ids" => Array(
-							$row[0]
+							$stop['stop_id']
 						) ,
 						"stop_codes" => Array(
-							$row[5]
+							$stop['stop_code']
 						)
 					);
 				}
 				else {
 					// subsequent duplicates
-					$stopsGrouped["stop_ids"][] = $row[0];
+					$stopsGrouped["stop_ids"][] = $stop['stop_id'];;
 				}
 			}
 		}

file:a/trip.php -> file:b/trip.php
--- a/trip.php
+++ b/trip.php
@@ -1,93 +1,86 @@
 <?php
 include ('include/common.inc.php');
-$tripid = filter_var($_REQUEST['tripid'], FILTER_SANITIZE_NUMBER_INT);
-$stopid = filter_var($_REQUEST['stopid'], FILTER_SANITIZE_NUMBER_INT);
-$routeid = filter_var($_REQUEST['routeid'], FILTER_SANITIZE_NUMBER_INT);
+
 $routetrips = Array();
-if ($_REQUEST['routeid'] && !$_REQUEST['tripid']) {
-	$tripid = 0;
-	$url = $APIurl . "/json/routetrips?route_id=" . $routeid;
-	$routetrips = json_decode(getPage($url));
-	foreach ($routetrips as $trip) {
-		if ($trip[2] > midnight_seconds()) {
-			$tripid = $trip[1];
-			break;
-		}
-	}
-	if ($tripid == 0) $tripid = $routetrips[0][1];
+
+if (isset($routeid) && !isset($tripid)) {
+    $trip = getRouteNextTrip($routeid);
+    $tripid = $trip['trip_id'];
+} else {
+    $trip = getTrip($tripid);
+    $routeid = $trip["route_id"];
 }
-$url = $APIurl . "/json/triprows?trip=" . $tripid;
-$trips = array_flatten(json_decode(getPage($url)));
-if (sizeof($routetrips) == 0) {
-	$routeid = $trips[1]->route_id;
-	$url = $APIurl . "/json/routetrips?route_id=" . $trips[1]->route_id;
-	$routetrips = json_decode(getPage($url));
+
+$routetrips = getRouteTrips($routeid);
+    
+include_header("Stops on " . $trip['route_short_name'] . ' ' . $trip['route_long_name'], "trip");
+trackEvent("Route/Trip View","View Route",  $trip['route_short_name'] . ' ' . $trip['route_long_name'], $routeid);
+
+
+echo '<h2>Via:</h2> <small>' . viaPointNames($tripid) . '</small>';
+echo '<h2>Other Trips:</h2> ';
+foreach (getRouteTrips($routeid) as $othertrip) {
+	echo '<a href="trip.php?tripid=' . $othertrip['trip_id'] . "&amp;routeid=" . $routeid . '">' . str_replace("  ",":00",str_replace(":00"," ",$othertrip['arrival_time'])). '</a> ';
 }
-include_header("Stops on " . $trips[1]->route_short_name . ' ' . $trips[1]->route_long_name, "trip");
-trackEvent("Route/Trip View","View Route", $trips[1]->route_short_name . ' ' . $trips[1]->route_long_name, $trips[1]->route_id);
-$url = $APIurl . "/json/tripstoptimes?trip=" . $tripid;
-$json = json_decode(getPage($url));
-$stops = $json[0];
-$times = $json[1];
-$viaPoints = Array();
-foreach ($stops as $stop) {
-	if (!startsWith($stop[5], "Wj")) {
-		$viaPoints[] = $stop[1];
-	}
+flush(); @ob_flush();
+echo '<h2>Other directions/timing periods:</h2> ';
+foreach (getRoutesByNumber($trip['route_short_name']) as $row) {
+	if ($row['route_id'] != $routeid) echo '<a href="trip.php?routeid=' . $row['route_id'] . '">' . $row['route_long_name'] . ' (' . ucwords($row['service_id']) . ')</a> ';
 }
-echo '<p><h2>Via:</h2> ' . implode(", ", $viaPoints) . '</small></p>';
-echo '<p><h2>Other Trips:</h2> ';
-foreach ($routetrips as $othertrip) {
-	echo '<a href="trip.php?tripid=' . $othertrip[1] . "&routeid=" . $routeid . '">' . midnight_seconds_to_time($othertrip[0]) . '</a> ';
-}
-echo '</p><p><h2>Other directions/timing periods:</h2> ';
-$url = $APIurl . "/json/routesearch?routeshortname=" . rawurlencode($trips[1]->route_short_name);
-$json = json_decode(getPage($url));
-foreach ($json as $row) {
-	if ($row[0] != $routeid) echo '<a href="trip.php?routeid=' . $row[0] . '">' . $row[2] . ' (' . ucwords($row[3]) . ')</a> ';
-}
+flush(); @ob_flush();
 echo '  <ul data-role="listview"  data-inset="true">';
-echo '<li data-role="list-divider">' . midnight_seconds_to_time($times[0]) . '-' . midnight_seconds_to_time($times[sizeof($times) - 1]) . ' ' . $trips[1]->route_long_name . '</li>';
 $stopsGrouped = Array();
-foreach ($stops as $key => $row) {
-	if (($stops[$key][1] != $stops[$key + 1][1]) || $key + 1 >= sizeof($stops)) {
+$tripStopTimes = getTimeInterpolatedTrip($tripid);
+echo '<li data-role="list-divider">' . $tripStopTimes[0]['arrival_time'] . ' to ' . $tripStopTimes[sizeof($tripStopTimes) - 1]['arrival_time'] . ' ' . $trips[1]->route_long_name . '</li>';
+
+foreach ($tripStopTimes as $key => $tripStopTime) {
+	if (($tripStopTimes[$key]["stop_name"] != $tripStopTimes[$key + 1]["stop_name"]) || $key + 1 >= sizeof($tripStopTimes)) {
 		echo '<li>';
-		if (!startsWith($row[5], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point" class="ui-li-icon">';
+		if (!startsWith($tripStopTime['stop_code'], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point" class="ui-li-icon">';
 		if (sizeof($stopsGrouped) > 0) {
 			// print and empty grouped stops
 			// subsequent duplicates
-			$stopsGrouped["stop_ids"][] = $row[0];
-			$stopsGrouped["endTime"] = $times[$key];
+			$stopsGrouped["stop_ids"][] = $tripStopTime['stop_id'];
+			$stopsGrouped["endTime"] = $tripStopTime['arrival_time'];
 			echo '<a href="stop.php?stopids=' . implode(",", $stopsGrouped['stop_ids']) . '">';
-			echo '<p class="ui-li-aside">' . midnight_seconds_to_time($stopsGrouped['startTime']) . ' to ' . midnight_seconds_to_time($stopsGrouped['endTime']) . '</p>';
-			echo bracketsMeanNewLine($row[1]);
+			echo '<p class="ui-li-aside">' . $stopsGrouped['startTime'] . ' to ' . $stopsGrouped['endTime'];
+                        echo '</p>';
+                        if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
+						echo '<span class="ui-li-count">' . distance($stop['stop_lat'],$stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
+					}
+			echo bracketsMeanNewLine($tripStopTime["stop_name"]);
 			echo '</a></li>';
+                        flush(); @ob_flush();
 			$stopsGrouped = Array();
 		}
 		else {
 			// just a normal stop
-			echo '<a href="stop.php?stopid=' . $row[0] . (startsWith($row[5], "Wj") ? '&stopcode=' . $row[5] : "") . '">';
-			echo '<p class="ui-li-aside">' . midnight_seconds_to_time($times[$key]) . '</p>';
-			echo bracketsMeanNewLine($row[1]);
+			echo '<a href="stop.php?stopid=' . $tripStopTime['stop_id'] . (startsWith($tripStopTime['stop_code'], "Wj") ? '&amp;stopcode=' . $tripStopTime['stop_code'] : "") . '">';
+			echo '<p class="ui-li-aside">' . $tripStopTime['arrival_time'] . '</p>';
+			if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
+						echo '<span class="ui-li-count">' . distance($stop['stop_lat'],$stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
+					}
+                                        echo bracketsMeanNewLine($tripStopTime['stop_name']);
 			echo '</a></li>';
+                        flush(); @ob_flush();
 		}
 	}
 	else {
 		// this is a duplicated line item
-		if ($key - 1 <= 0 || ($stops[$key][1] != $stops[$key - 1][1])) {
+		if ($key - 1 <= 0 || ($tripStopTimes[$key]['stop_name'] != $tripStopTimes[$key - 1]['stop_name'])) {
 			// first duplicate
 			$stopsGrouped = Array(
-				"name" => $row[1],
-				"startTime" => $times[$key],
+				"name" => $tripStopTime['stop_name'],
+				"startTime" => $tripStopTime['arrival_time'],
 				"stop_ids" => Array(
-					$row[0]
+					$tripStopTime['stop_id']
 				)
 			);
 		}
 		else {
 			// subsequent duplicates
-			$stopsGrouped["stop_ids"][] = $row[0];
-			$stopsGrouped["endTime"] = $times[$key];
+			$stopsGrouped["stop_ids"][] = $tripStopTime['stop_id'];
+			$stopsGrouped["endTime"] = $tripStopTime['arrival_time'];
 		}
 	}
 }

--- a/tripPlanner.php
+++ b/tripPlanner.php
@@ -1,8 +1,8 @@
 <?php
 include ('include/common.inc.php');
 include_header("Trip Planner", "tripPlanner", true, true, true);
-$from = (isset($_REQUEST['from']) ? filter_var($_REQUEST['from'], FILTER_SANITIZE_STRING) : "Brigalow");
-$to = (isset($_REQUEST['to']) ? filter_var($_REQUEST['to'], FILTER_SANITIZE_STRING) : "Barry");
+$from = (isset($_REQUEST['from']) ? filter_var($_REQUEST['from'], FILTER_SANITIZE_STRING) : "");
+$to = (isset($_REQUEST['to']) ? filter_var($_REQUEST['to'], FILTER_SANITIZE_STRING) : "");
 $date = (isset($_REQUEST['date']) ? filter_var($_REQUEST['date'], FILTER_SANITIZE_STRING) : date("m/d/Y"));
 $time = (isset($_REQUEST['time']) ? filter_var($_REQUEST['time'], FILTER_SANITIZE_STRING) : date("H:i"));
 function formatTime($timeString) {
@@ -62,6 +62,7 @@
 			echo '<li>';
 			processLeg($legNumber, $leg);
 			echo "</li>";
+                        flush(); @ob_flush();
 		}
 		echo "</ul>";
 	}
@@ -124,23 +125,24 @@
 	}
 	else {
 		$url = $otpAPIurl . "ws/plan?date=" . urlencode($_REQUEST['date']) . "&time=" . urlencode($_REQUEST['time']) . "&mode=TRANSIT%2CWALK&optimize=QUICK&maxWalkDistance=840&wheelchair=false&toPlace=$toPlace&fromPlace=$fromPlace&intermediatePlaces=";
-		$ch = curl_init($url);
+		debug($url);
+                $ch = curl_init($url);
 		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 		curl_setopt($ch, CURLOPT_HEADER, 0);
 		curl_setopt($ch, CURLOPT_HTTPHEADER, array(
 			"Accept: application/json"
 		));
-		curl_setopt($ch, CURLOPT_TIMEOUT, 5);
+		curl_setopt($ch, CURLOPT_TIMEOUT, 10);
 		$page = curl_exec($ch);
-		if (curl_errno($ch)) {
-			tripPlanForm("Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch) .(isDebug() ? $url : ""));
+		if (curl_errno($ch) || curl_getinfo($ch, CURLINFO_HTTP_CODE) != 200) {
+			tripPlanForm("Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch) . " ". curl_getinfo($ch, CURLINFO_HTTP_CODE) .(isDebug() ? "<br>".$url : ""));
                         trackEvent("Trip Planner","Trip Planner Failed", $url);
                 }
 		else {
                   	trackEvent("Trip Planner","Plan Trip From", $from);
                         trackEvent("Trip Planner","Plan Trip To", $to);
 			$tripplan = json_decode($page);
-			debug(print_r($triplan, true));
+			debug(print_r($tripplan, true));
 			echo "<h1> From: {$tripplan->plan->from->name} To: {$tripplan->plan->to->name} </h1>";
 			echo "<h1> At: ".formatTime($tripplan->plan->date)." </h1>";
 			if (is_array($tripplan->plan->itineraries->itinerary)) {

file:b/updatedb.php (new)
--- /dev/null
+++ b/updatedb.php
@@ -1,1 +1,60 @@
+<?php
+include('include/common-db.inc.php');
+// Unzip cbrfeed.zip, import all csv files to database
+$unzip = true;
+$zip = zip_open(dirname(__FILE__) . "/cbrfeed.zip");
+$tmpdir = "/tmp/cbrfeed/";
+mkdir($tmpdir);
+if ($unzip) {
+if (is_resource($zip)) {
+	while ($zip_entry = zip_read($zip)) {
+		$fp = fopen($tmpdir . zip_entry_name($zip_entry) , "w");
+		if (zip_entry_open($zip, $zip_entry, "r")) {
+			echo "Extracting " . zip_entry_name($zip_entry) . "\n";
+			$buf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry));
+			fwrite($fp, "$buf");
+			zip_entry_close($zip_entry);
+			fclose($fp);
+		}
+	}
+	zip_close($zip);
+}
+}
+                        
+foreach (scandir($tmpdir) as $file) {
+	if (!strpos($file, ".txt") === false) {
+		$fieldseparator = ",";
+		$lineseparator = "\n";
+		$tablename = str_replace(".txt", "", $file);
+		echo "Opening $file \n";
+		$line = 0;
+		$handle = fopen($tmpdir . $file, "r");
+		while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
+			if ($line > 0) {
+				$query = "insert into $tablename values(";
+                                $valueCount = 0;
+                                foreach ($data as $value) {
+                                $query.=($valueCount >0 ? "','" :"'").pg_escape_string($value);
+                                $valueCount++;
+                                }
+				if ($tablename == "stops") {
+                                $query.= "', ST_GeographyFromText('SRID=4326;POINT({$data[2]} {$data[0]})'));";
+				} else {
+                                  $query.= "');";
+                                }
+                                if ($tablename =="stop_times" && $data[1] == "") {
+                                  $query = "insert into $tablename (trip_id,stop_id,stop_sequence) values('{$data[0]}','{$data[3]}','{$data[4]}');";
+                                }
+                                 
+			}
+                        $result = pg_query($conn, $query);
+			$line++;
+                        if ($line % 10000 == 0) echo "$line records... \n";
+		}
+		fclose($handle);
+		echo "Found a total of $line records in $file.\n";
 
+	}
+}
+?>
+

file:a/view.sh (deleted)
--- a/view.sh
+++ /dev/null
@@ -1,6 +1,1 @@
- #!/bin/sh
- f=`dirname $0`
- cd $f
- python schedule_viewer.py --feed=/var/www/cbrfeed.zip \
---key=ABQIAAAA95XYXN0cki3Yj_Sb71CFvBTPaLd08ONybQDjcH_VdYtHHLgZvRTw2INzI_m17_IoOUqH3RNNmlTk1Q