var rb = rb || {};
        rb.language = {
   "replenish.error_email.discontinued.subject" : "Your Estee Lauder Auto-replenishment item has been discontinued.",
   "questions_sent" : "Thank you for contacting Estee Lauder Online. We value your interest and look forward to serving you. If your question or concern is urgent, please call us at 1- 877-311-3883 and we will be happy to assist you. <br><br>Please note: Your Internet Service Provider (ISP) or email client may be using filters to block email and they may affect the receipt of email from Estee Lauder Online. To be sure that our response reaches you, please consult the help section of your email program or contact your ISP directly to see how to relax or remove the settings that filter or block email. Additional Information is available in our FAQ section. If you have further questions, please contact us at 1- 877-311-3883.",
   "question_4" : "How to apply an Estée Lauder product",
   "session_timeout" : "You have been signed out due to inactivity. Please sign in again.",
   "dashboard_3step_header" : "Your custom-fit 3-Step for Skin Type",
   "po_reset" : "Click here to reset and assist another customer",
   "email" : "Email:",
   "your_order_summary" : "Your Order Summary",
   "verify_email" : "Please verify your Email Address",
   "respect_privacy" : "By submitting your information, you agree to our <a href=\"/cms/customer_service/terms.tmpl\">Terms and Conditions</a> and <a href=\"/cms/customer_service/privacy.tmpl\">Privacy Policy</a>.",
   "nav_favorites" : "Favorites",
   "Items" : "[number of items] items in your cart",
   "payment_option_mc" : "Mastercard",
   "invalid_password.password" : "config The password you supplied is incorrect.",
   "taxinfo" : "Tax Info",
   "go_shopping" : "Go shopping!",
   "replenish.error_email_es.reminder_error.notification" : "El propósito del siguiente email es informarle de que en este momento no podemos procesar su orden de reabastecimiento por el siguiente motivo:",
   "replenishment.service.meta_desc" : "Replenishment Service",
   "replenish.error_email.cc_expired" : "Your credit card has expired.",
   "your_default_billing_method" : "Your Default Billing Method",
   "chars" : "NG! characters remaining",
   "title" : "Title",
   "sign_in" : "Sign In",
   "skin_type_dry_normal" : "Dry/Normal",
   "security_answer" : "Answer",
   "default_shipping_message_shaded" : "Free Standard Shipping<br/>with any $50 order",
   "preorder_prod_default_shipping_message" : "PRE-ORDER: Order today to reserve yours!",
   "previous" : "Previous",
   "account_not_name" : "If you are not |first_name|, <a href=\"/account/signin.tmpl?_SUBMIT=signout\">click here</a>",
   "sign_up_pc_email" : "Get the latest news and offers:",
   "order_thanks" : "Thank you for your order.",
   "dashboard_concern_label2" : "Redness",
   "add_gift_message" : "Click here to add a FREE gift message.",
   "product_reco" : "Product recommendations",
   "CLOSE" : "Close",
   "your_temp_pass" : "Your temporary password is: abc1234",
   "address2" : "Address 2",
   "contact_us" : "Contact Us",
   "Nav_Welcome_Back" : "Hi, Emiko | first name |",
   "new_customers" : "New Customers",
   "mascara" : "Mascara",
   "edit_personal_info_h" : "Edit personal information <br>and passwords",
   "isp_note" : "PLEASE NOTE: Your Internet Service Provider (ISP) or e-mail client may be using filters to block e-mail that may affect the receipt of e-mail from esteelauder.com. To be sure that our response reaches you, please consult the help section of your e-mail program or contact your ISP directly to see how to relax or remove the settings that filter or block e-mail. This will ensure that correspondence from esteelauder.com will be accepted. Additional Information is available in our FAQ section above. If you have further questions, please contact us at 877-311-3883.",
   "deaging.name" : "Anti-Aging ",
   "click_gift_wrap_txt" : "If you would like to choose gift wrap, <a href=\"#\">click here</ a>",
   "nav.unevenskintone" : "Uneven Skin Tone",
   "order_review.page_title" : "Order Review",
   "nav.dailysunprotection" : "Daily Sun Protection",
   "Fields_are_required" : "Indicates required field",
   "default_pay_option" : "Default Payment Option",
   "nav.eyeshadow" : "Eye Shadow",
   "address" : "Address",
   "PRESS" : "PRESS",
   "store_service" : "Service at a [BRAND] Counter or Store",
   "email_ordernp_2" : "Si está interesada(o) en restablecer su servicio de reabastecimiento,",
   "nav.allmensgrooming" : "Grooming",
   "cancel_fulfiller" : "cancel in progress",
   "Skin_Concern" : "Skin Concerns",
   "Pending" : "Pending",
   "cod_commission" : "COD Commission:",
   "fragrance" : "Fragrance",
   "expert_tips_trends.articles.name" : " Articles",
   "zip" : "Postal Code",
   "displaying_products_of" : "of",
   "customer_email_greeting" : "Thank you for taking the time to contact us at esteelauder.com",
   "personal_information" : "Personal Information",
   "status" : "Status",
   "gift_with_purchase" : "Gift With Purchase events in my area",
   "results_range" : "Results 1 to ::max_per_page:: of ::total_results::",
   "order_shipped" : "Shipped",
   "share" : "Share this product",
   "customer_service_txt" : "If you need help or would like to order by phone, call customer service at 1-800-419-4041",
   "need_assistance_short" : "If you need assistance, please call Customer Service at 877-311-3883.",
   "option" : "Option",
   "starred_password_hint" : "*Password hint",
   "cartbox.name" : "eGift Card:",
   "giftwrap_discount" : "Giftwrap Discount",
   "account.account_profile" : "Account Profile | Estée Lauder",
   "sets_gifts" : "Sets & Gifts",
   "po_place_an_order" : "Click here to place an order for this customer",
   "delivery_every_03" : "Delivery Every 60 Days",
   "pass_request_header" : "If you still do not know the password",
   "startdate" : "Start date",
   "nav.makeup" : "Makeup",
   "edit.profile.meta_desc" : "Edit your [BRAND] online profile.",
   "Size" : "Size",
   "sign_in_with_pw_txt" : "We have sent you your temporary password, so please login using your temporary password to reset your password.",
   "create_account_h" : "Create an Account",
   "email_thirtydays_1" : "Le envíamos un email recientemente informándole de que no hemos podido procesar su orden de reabastecimiento de [Brand product name]de Estee Lauder Online. No hemos recibido noticias suyas y queríamos enviarle un recordatorio por si nunca hubiera recibido nuestro email. Si está interesada(o) en seguir recibiendo la orden de reabastecimiento de",
   "cl_review_sub_title" : " [BRAND] | Review Submission Page",
   "Delete" : "Delete",
   "yes_gift_wrapped" : "This order will be gift wrapped.",
   "required.first_name" : "config Please supply your first name.",
   "cc_expiration_date" : "Expiration",
   "once_completed_email_txt" : "We will email you once we've shipped your order.",
   "Search_Results" : "Search Results",
   "integer.birth_year" : "Birth Year",
   "forgot_password" : "Forgot Password?",
   "sign_up_confirm_h" : "Thank you for signing up with [BRAND].com",
   "migrated_user_agree_txt" : "By clicking on 'Submit' below, you are acknowledging that you have read, understand, and agree to be bound by the <a class=\"overlay-link overlay-height-350 overlay-width-640\" href=\"/cms/customer_service/terms.tmpl\">Terms and Conditions</a> and <a class=\"overlay-link overlay-height-350 overlay-width-640\" href=\"/cms/checkout/popup/security_popup.tmpl \">Privacy Policy</a> for this website.",
   "Click_gift_wrap_txt" : "If you would like to choose gift wrap, <a href=\"#\">click here</a>",
   "order_tracking_info_txt" : "Select an order number below to get more information regarding your order. <br> If you need more onformation, click on the tracking number to track your order's shipping progress. <br> (Multiple tracking numbers per order indicate seperate shipping dates.)",
   "pls_first_name" : "Please enter your first name.",
   "mens_3step" : "Men's 3-step",
   "locs_near" : "locations near you.",
   "promo_message_achieved" : "Congratulations! Your order qualifies for FREE Standard Shipping. No offer code needed.",
   "Shipping_Information" : "Our Shipping Methods / Charges",
   "replenishment_detail_intro" : "Please note: if you make any changes to your auto-replenishment order, <br/>you must click \"Update\" for your changes to take effect.",
   "add_address" : "Add New Address",
   "preorder_message.cart_item" : "PRE-ORDER: Order today to reserve yours! As soon as it is available and ready for shipment, we’ll notify you via email.",
   "next_delivery" : "Next delivery",
   "alt.flag_image_preorder" : "PRE-ORDER",
   "All_Shades" : "All Shades",
   "concern" : "Concern",
   "why_create_account" : "Why do I need to create an account?",
   "replenishment_frequency_90_days" : "Deliver every 90 days",
   "About_You" : "About You",
   "powder" : "Powder",
   "our_guarantee_txt" : "If you are not satisfied with your order for any reason, we will be happy to accept a return or exchange. We proudly stand behind every product we sell.",
   "BRAND_USER" : "brand user?",
   "customerservice" : "CUSTOMER SERVICE",
   "terms_agreement_end" : " for this website.",
   "Benefit_Priority" : "Product Features",
   "sign_up" : "Sign Up",
   "gc_nav_buygiftcard" : "Buy Gift Card",
   "email_address_verify" : "Email Address Verify",
   "color_group" : "Color Group",
   "invalid.mobile_email_address" : "config Please enter a PC email address in the following format jane@gmail.com.",
   "pass_sent_next_time" : "pass_sent_next_time.copy needed for this key",
   "replenish.error_email.cancellation_warning.customer_action" : "We haven’t heard from you and wanted to send a reminder in case our email never reached you. If you would like to continue receiving your replenishment order for ::PRODUCT::, <a href=\"https://::SERVERNAME::/account/replenish/\">click here</a> to update your information and we will be happy to process your next order. After 30 days, we will assume you wish to cancel this replenishment order. <br>If you have questions or if you need assistance, please contact us at <a href=\"mailto:elonlineservice@esteelauder.com\" style=\"text-decoration:none;color:#103B76;\">ELOnlineService@esteelauder.com</a> or by phone at 877-311-3883.",
   "state" : "State/Province",
   "without_tax" : "without tax",
   "MESSAGE" : "Please limit the length of the giftcard message to 215 characters.",
   "makeup.scat.href" : "/cms/product/supercat/makeup.tmpl",
   "EXPERT_TIPS" : "3XPERT_TIPS",
   "removing" : "Removing",
   "account.orders.page_title" : "All Orders",
   "address_book_new.page_title" : "Address Book",
   "price" : "Price",
   "replenishing_product" : "Replenishing Product",
   "out_of" : "out of",
   "order_conf_txt" : "When your order is shipped, you will receive a shipping confirmation from us. <br>If you have any questions, please call us at 1-877-311-3883.",
   "card_nickname" : "Card Nickname",
   "No_Addresses_Saved" : "You have no shipping address on file. Please click on \"Make This My Default Shipping Address\" to designate one, or click on the ADD NEW ADDRESS button above.",
   "cancel_in_process" : "cancel in progress",
   "Gift_Options" : "Gift Note/Gift Wrap",
   "signup" : "Email Address",
   "nav.3stepskincare" : "3-Step Skin Care",
   "recent_inquiry_submitted" : "You recently submitted an inquiry regarding your registered password.",
   "if_you_are_not" : "If you are not",
   "dullness" : "Dullness",
   "nav.chemistry" : "Chemistry",
   "Skin_Type" : "SkinType",
   "email_us" : "Email Us",
   "default_shipping_address" : "Set as my default shipping address",
   "how_you_use_esteelauder_com" : "How do you use Estee Lauder",
   "question_6" : "Where can I buy Estée Lauder products near me?",
   "password_hint_starred" : "New password hint*",
   "check_bal-js" : "Check your e-Gift Card balance",
   "question_2" : "Questions about Bridal Looks",
   "quantity" : "Quantity",
   "title2" : "Mr",
   "nav.egiftcards" : "eGift Cards",
   "shipping_message" : "Shipping message",
   "replenishment_frequency_60_days" : "Deliver every 60 days",
   "Edit_this_address" : "Edit This Address",
   "phone_include_area_note" : "Include area code.Use hyphens to separate, as in 877-555-1234",
   "Cancel" : "Cancel",
   "tax_included_above" : "(Tax Included above)",
   "review_unavail" : "Review Submission Currently Unavailable",
   "products" : "Products",
   "email_discountinued_1" : "ha sido discontinuado, por tanto debemos cancelar el servicio de reabastecimiento de este producto. Lamentamos las molestias que esto pueda causarle. Si necesita ayuda para encontrar una alternativa adecuada para este producto, póngase en contacto con nosotros por medio de email, hable en directo Online con una Consejera de Belleza o llámenos al 877-311-3883 (servicio 24 horas).",
   "add_billing_method" : "Add Billing Method",
   "all_skintypes" : "All Skintypes",
   "delivery_date_dropdown_default" : "Earliest available delivery",
   "brand_newsletter_h" : "[BRAND] Newsletter",
   "email_signup_text" : "Email Sign Up",
   "Price" : "Price",
   "pending" : "pending",
   "new_password" : "New Password",
   "emailus.href" : "/templates/customer_service/contact_us.tmpl",
   "Phone" : "Home Phone",
   "sun" : "Body & Suncare",
   "products.home.meta_desc" : "Estée Lauder Official Site Shop now for proven-effective skincare, sophisticated makeup and favorite fragrances from Estée Lauder. Every woman can be beautiful.",
   "one_item_in_cart" : "There is one item in your shopping bag.",
   "dashboard_concern_label1" : "Acne",
   "email_promotions_list_items" : "<li><span>Special Offers</span> sent to your inbox</li><li>New Product Previews</li><li>Tips, Trends, &amp; More</li>  ",
   "items_in_cart" : "There are ::items:: items in your cart.",
   "reorder_past_purchase" : "Re-order from past purchases",
   "Enter_password" : "Enter Password",
   "upgrade_flash" : "You need to upgrade your Flash Player",
   "select_shades_purchase" : "Select Shades & Purchase",
   "Qty" : "Quantity",
   "Mobile_Email_Address" : "Mobile Email Address",
   "email_alerts" : "Email alerts",
   "logout_click_here" : "please <a href=\"/account/signin.tmpl?_SUBMIT=signout\">click here</a>.",
   "invoice_sent_to" : "Invoice sent to:",
   "replenishment_notice.item" : "This item will be automatically shipped and charged according to your selected frequency.",
   "promo_message" : "Purchase $30.00 more to qualify for FREE Standard Shipping.",
   "nav.exfoliators_masks" : "Exfoliation/ Peeling & Masken",
   "update" : "Change Shipping Date",
   "6_12_numbers_letters" : "6 to 12 digits, or 6 - 12 characters",
   "Delivery_Options" : "Delivery Method",
   "Profile_Info" : "Profile Info",
   "about_mail_magazine" : "About E-mail Newsletter",
   "pass_request_contact" : "<p>Follow the instructions in the email to reset your password then access your esteelauder.com account.</p> <p>If you need assistance, please call Customer Service at 877-311-3883 and we will help you set a new password.</p>",
   "next" : "Next",
   "your_email_field" : "Your Email",
   "r_u_reg" : "Are you a registered member of esteelauder.com?",
   "Home" : "Home",
   "shipping_total_overnight" : "Overnight Shipping",
   "account_number" : "Account Number:",
   "skin_type_sensitive" : "Sensitive",
   "pls_confirm_email" : "Please confirm your email address.",
   "Thank_You_For_Your_Order" : "Thank You for Your Order",
   "enter_cc_year" : "Please supply the year when your credit card expires.",
   "add_gift_wrap_label" : "Select this box if you'd like your order gift wrapped. When you select gift wrap your entire order will be wrapped and shipped together.",
   "order_in_process" : "In Process",
   "edit_payment_option" : "Edit Payment Option",
   "verify_email_address_starred" : "*Verify Email Address",
   "gc_nav_chkbalance" : "Check Balance",
   "replenish.error_email.invalid_payment_address" : "You have no billing address on file for this order.",
   "po_email_address" : "Email Address",
   "delivery_address" : "Shipping Address",
   "expert_tips_trends.href" : "/cms/expert_tips_trends/index.tmpl",
   "starred_first_name" : "*First Name",
   "enter_email" : "In regard to verification, please input email address once more.",
   "edit_profile.page_title" : "edit_profile.page_title.copy needed for this key",
   "payment_debit" : "Debit",
   "mobile_phone" : "Mobile Phone",
   "email_orderconfirm_2" : "Su orden de reabastecimiento ha sido enviada y se procesará en 1-2 días hábiles.",
   "overall_rating" : "Rating",
   "default_bill_address" : "Your Default Shipping Address",
   "address_book_h" : "Address Book",
   "customer_service_h" : "Customer Service",
   "email_preorder_6" : "Si desea cambiar la frecuencia, cambiar o eliminar un artículo de sus órdenes de reabastecimiento, o bien cancelar su servicio de reabastecimiento o revisar el estado de sus órdenes de reabastecimiento,",
   "shipping_total" : "Standard Shipping",
   "estimated_total" : "Estimated Total",
   "current_order" : "Current Order Status",
   "city" : "City",
   "free" : "Free",
   "sign_up_header" : "Sign Up now!",
   "enter_cc_month" : "Please supply the month when your credit card expires.",
   "reset_pass_txt_p03" : "Please do not reply to this message. If you have received this message in error, please forward a copy of the entire message, including the headers to, [BRAND]onlinetech@[BRAND].com so we can correct this matter. We apologize for any inconvenience.",
   "replenish.error_email_es.reminder_error.customer_action" : "Si fuera tan amable de ponerse en contacto con nosotros con datos actualizados, estaremos encantados de continuar con el proceso de su orden de reabastecimiento. Llámenos al 877-311-3883 o responda a este email si necesita ayuda. No envíe información sobre su tarjeta de crédito por email ya que no es un método seguro para transmitir información personal delicada.También puede  hacer clic aquí  para actualizar sus datos del servicio de reabastecimiento en “Mi Cuenta”.",
   "nav_account_profile" : "Account Profile",
   "limited_edition" : "Limited Edition",
   "quantity_col_header" : "Quantity",
   "email_gifts_instore" : "Yes, I would like to receive email about Gifts With Purchase, In-Store activities and Special Events.",
   "your_order_txt" : "Your order is not complete until<br>you click SUBMIT ORDER.",
   "your_address_colon" : "Your Address:",
   "delivery_every_01" : "Delivery Every 30 Days",
   "replenish.detail.invalid_shipping" : "You have no shipping address on file for this order. Please choose an address from the address book at right, or create a new one by clicking on Add New Address.",
   "based_on" : "Based on your search criteria, there are",
   "items_discount" : "Discount",
   "tracking_link" : "tracking link",
   "All" : "All",
   "security_and_privacy" : "Security & Privacy",
   "order_confirmation" : "Order Confirmation",
   "skincare" : "Skincare",
   "replenishment_detail_h" : "My Replenishment Detail",
   "read_reviews_spanish" : "Leer comentarios",
   "order_status_h" : "Order Status",
   "click_here" : "click here.",
   "choose" : "Choose",
   "delivery_time_dropdown_default" : "Any time",
   "global_search" : "Search",
   "expiration" : "Expiration: [mm/yy]",
   "expert_tips_trends.videos.href" : "/cms/expert_tips_trends/how_to/video_landing.tmpl",
   "HOW_TO_USE" : "Use",
   "Add_gift_wrap_label" : "Add gift wrap",
   "signin_to_write_review" : "Sign In To Write A Review",
   "customer_email_salutation" : "esteelauder.com Customer Service",
   "question_13" : "Ingredient Inquiries",
   "question_1" : "If available, please provide your Order Number",
   "PC_colon" : "PC:",
   "Newsletter" : "Newsletter",
   "set_as_default_billing" : "Set as my default billing address",
   "select_delivery_time" : "Select a delivery time",
   "pass_sent_back_to_login" : "Back to Sign In",
   "orders" : "Orders",
   "replenish.error_email.order_error.notification" : "We regret to inform you that we are currently unable to process your auto-replenishment order. An email was sent previously to alert you to this issue and to request that you visit our Web site to update your payment or shipping information. We regret that we have not received your response and the information in your account has not been updated as requested.",
   "temp_password_sub" : "Your password will be emailed to you.",
   "RETURN_TO_SHOPPING" : "Return To Shopping",
   "no_favorites_message" : "You have no favorites. Text here about how convenient this is. Simply click on the “add to favorites” button on your favorite product.",
   "dec" : "Dec",
   "nav.welcome" : "Welcome",
   "max_purchasing" : "Maximum Purchase Policy",
   "beauty_advice" : "BEAUTY ADVICE AND PRODUCT APPLICATION TIPS",
   "address_1" : "Address Line 1:",
   "store_loc_h" : "Store & Event Locator",
   "street_addr" : "Street Address:",
   "cartbox.email" : "E-mail address to receive",
   "cancel_requested" : "cancel requested",
   "Finish" : "Finish",
   "nav.moisturizers" : "Moisturizers",
   "country" : "Country",
   "order_status_6" : "Cancelled",
   "email_preorder_3" : "Su orden de reabastecimiento de Estee Lauder Online será cargada en su tarjeta de crédito en la fecha de envío.",
   "dashboard_concern_label6" : "Age Prevention",
   "reset_pass_txt" : "To keep your personal information secure, please set a new password to access your account.",
   "fields_required" : "Required",
   "Nav_Not_Name" : "(not #|first_name|?)",
   "BEST_SELLERS" : "Bestseller",
   "select_dash_options" : "Select the options you'd like to be displayed in you’re My [BRAND] dashboard:",
   "gifts" : "Gifts",
   "order.confirmation.meta_desc" : "Order Confirmation",
   "po_currently_signed_in_csr" : "<i>You are currently signed in with csr account</i>",
   "no_gift_message" : "No gift message",
   "password_criteria_txt" : "The password is case-sensitive. Please enter 6-12 characters.",
   "most_recent_order" : "Most recent order:",
   "customer_email_salutation_beautyadvice" : "<br/><br/>Sincerely, <br/>&nbsp;&nbsp;&nbsp;&nbsp;The team at <a href=\"http://esteelauder.com\">esteelauder.com</a>",
   "skin_concern" : "Skin Concerns",
   "payment_cod" : "COD",
   "err_reset_password" : "Still can't remember? <a id=\"pwd-reset\" href=\"javascript:void(0);\">Click here</a> to reset your password.",
   "invalid.password.password_reset" : "We recently emailed you a temporary password. Please enter it as your current password and enter a new password below.",
   "no_registration_address_saved" : "You have no profile address on file. <a href=\"/account/address_book/index.tmpl\">Click here</a> to add it.",
   "cancel" : "Cancel",
   "account_landing_no_address_saved" : "This address is not on file. <a href=\"/account/address_book/\">Click here</a>  to add it.",
   "pass_sent_header" : "Password has been sent",
   "benefits" : "Product Features",
   "return_order_history" : "Return to Order History",
   "nav.redness" : "Redness",
   "account_info" : "CREATE YOUR USERNAME & PASSWORD",
   "your_default_payment" : "Your default shipping address",
   "Sign_In" : "Sign in",
   "Mobile_Phone" : "Mobile Phone",
   "spam" : "SPAM filters and ISPs",
   "Golden_Undertone" : "Golden_Undertone",
   "nav.acne" : "Acne",
   "billing_method" : "Billing Method",
   "enter_firstname" : "Please enter your First Name",
   "err_please_sign_in" : "Please sign in below.",
   "Delete_Address" : "Delete Address",
   "verify_new_password" : "Verify New Password",
   "sign_up_confirm_txt_p03" : "To thank you, we're giving you free standard shipping on your next [BRAND].com order of $50 or more*--just enter offer code [BRAND]28 AT CHECKOUT. This code is associated with the mail address you gave us when you signed up.",
   "view_past_purch" : "View All Past Purchases",
   "view_details" : "View Details",
   "requiredor.mobile_email_address" : "config Please supply your mobile email address or your pc email address.",
   "password_note" : "Note: password is case-sensitive.",
   "select_message_options" : "Select the options for your [BRAND] messaging and alerts and how you'd like them to be delivered to you:",
   "denotes_required_field" : "* Denotes required field",
   "City_Town" : "City/Town",
   "PC_Email_Address" : "PC Email Address",
   "print" : "Print",
   "Password" : "Password",
   "business" : "Business",
   "newsletter_optin_thanks_desc2" : "We’ve updated your communication preferences.",
   "enter_for" : "Enter your user name and password to access order tracking, autoreplenishment status and personalized offers.",
   "Street_Block" : "Street/Block No.:",
   "male" : "Male",
   "email_preorder_4" : "Si la información anterior es correcta y desea recibir su orden programada de reabastecimiento de Estee Lauder Online, no debe hacer nada – su orden será enviada según lo planificado. Para su conveniencia, recibirá un aviso por email con los detalles del envío cuando su orden salga de nuestro centro.",
   "replenishment_service.page_title" : "Replenishment Service | Estee Lauder",
   "you_have_no_past_purchases" : "You have no past purchases.",
   "samples" : "Can you send me samples?",
   "find_stores" : "Find Stores and Events in your area",
   "email_sevendays_2" : "Si fuera tan amable de ponerse en contacto con nosotros con datos actualizados, estaremos encantados de continuar con el proceso de su orden de reabastecimiento. Llámenos al 877-311-3883 o responda a este email si necesita ayuda. No envíe información sobre su tarjeta de crédito por email ya que no es un método seguro para transmitir información personal delicada.",
   "payment_information" : "Payment Information",
   "female" : "Female",
   "shipmethod_option_overnight" : "Overnight",
   "pls_password_hint" : "Please enter a password hint",
   "nav.allmensshave" : "Shave",
   "sign_up_h" : "Sign Up Now with [BRAND].com",
   "Filter_by" : "Filter by",
   "all" : "All",
   "err_password_identical" : "The Password fields must be identical.",
   "payment_oi" : "COD",
   "account_profile" : "Account Profile",
   "return_approved" : "return approved",
   "related_order" : "Related Order",
   "add_new_payment_option" : "Add New Payment Option",
   "email_ordernp_1" : "Lamentamos informarle que en estos momentos no podemos procesar su orden de reabastecimiento. Previamente le habíamos enviado un email para advertirle de este problema y para pedirle que visite nuestro sitio Web para actualizar sus datos de pago o de envío. Sentimos no haber recibido su respuesta y que la información de su cuenta no haya sido actualizada como solicitábamos.",
   "shipping_frequency_info1" : "All auto-replenishment orders are shipped via standard delivery.  Please allow [7-9] days from the order date for handling and delivery.  You may change your next order date by clicking \"Change Next Order Date\" and clicking on the calendar that appears.",
   "po_yes" : "yes",
   "pay_method" : "Payment method",
   "Benefits" : "Product Features",
   "delivery_address_colon" : "Deliver to:",
   "Your_Address" : "Your Address",
   "DELIVERY_AND_OPTIONS" : "Delivery and Options",
   "delivery_day_info" : "Standard: 5-7 business days - $7.00",
   "skin_types" : "Skin Types",
   "click_shade" : "Click Shade Below to Select",
   "verify_password_starred" : "Verify your password*",
   "intl_fees_amt" : "International Fees and Handling",
   "remove" : "Remove",
   "enter_phone" : "Please enter a valid Phone Number.",
   "no" : "No",
   "nav_replenishment" : "Auto-Replenishment",
   "choose_a_different_sample" : "Choose a different sample.",
   "Edit_address_book_link" : "Click here to change your address.",
   "ORDER_REVIEW" : "Order Review",
   "current_password_starred" : "Current password*",
   "return_pending" : "Returned Pending",
   "cash_not_available" : " \tCash on Delivery is not available because this order is a gift.",
   "temp_password_txt" : "Due to the fact that you have forgotten your password, we will need to reset it with a new one as well as a hint should you forget your password in the future. We will email your current password to the email address below. You will then be able to use this password to set a new one.",
   "replenishment.instruction_copy" : "To make any changes to your auto-replenishment items, including shipping address,<br/>billing address, and frequency, please click on \"View/Edit Details\" below.",
   "may_we_suggest" : "May We Suggest",
   "payment_option_oi" : "COD",
   "total" : "Total",
   "replenish.error_email_es.order_error.notification" : "Lamentamos informarle que en estos momentos no podemos procesar su orden de reabastecimiento. Previamente le habíamos enviado un email para advertirle de este problema y para pedirle que visite nuestro sitio Web para actualizar sus datos de pago o de envío. Sentimos no haber recibido su respuesta y que la información de su cuenta no haya sido actualizada como solicitábamos.",
   "order_date" : "Order Date",
   "question_9" : "Service at a Estée Lauder Counter or Store",
   "return_requested" : "return requested",
   "alt.add_to_bag_btn.gif" : "Add to Cart",
   "address_submit" : "Saving…",
   "submit" : "Submit",
   "size_price" : "Size - Price",
   "nav_shopping_bag" : "Shopping Bag",
   "address_lookup" : "Check postal code",
   "reorder-btn-instructions" : "If you would like to re-order items you have purchased in the past, click on the “Re-order from My Past Purchases button below.",
   "products_interested" : "Products interested in",
   "po_start_shopping_as" : "Start shopping as:",
   "Gift_Message_Max_Characters" : "Up to 25 double byte characters per line",
   "view_past_orders" : "View past orders",
   "ORDER_CONFIRMATION" : "Order Confirmation",
   "view_edit_details" : "View/edit Details",
   "password.already.exists" : "Our records indicate that you have already registered with [BRAND]. Click here to have a temporary password emailed to you.",
   "exfoliators_masks" : "Exfoliators & Masks",
   "return_home" : "Register with",
   "product" : "Product",
   "Building" : "Building name:",
   "replenishment.no_items_info" : "You have no replenishment items.  Look for \"Replenishment Service\" while you shop for your favorite products. When you order with Replenishment Service, you'll automatically receive a new shipment of the item in intervals you select.  We'll charge your credit card for each order only when the item is shipped, and you may cancel at any time.To start the Replenishment Service, choose a delivery schedule during the checkout process.  You'll be asked to provide a shipping address, credit card, and billing address.  Once the order is placed, your first shipment will be processed and your Replenishment Service will be activiated.  Afterwards, you may change your replenishment schedule here at any time.",
   "no_items_in_cart" : "There are currently no items in your shopping cart",
   "nav.brows" : "Brows",
   "why_create_password" : "(Why do I need to create a password?)",
   "shipping_discount" : "Shipping Discount:",
   "Last_ordered" : "Last ordered",
   "example_mobile_phone" : "",
   "still_cant_remember" : "Still can't remember your password?",
   "replenish.error_email.reminder_error.notification" : "This email is being sent to inform you that we are currently unable to process your replenishment order at this time because of the following:",
   "payment_credit" : "Credit",
   "shopping_cart_h" : "Shopping Cart",
   "Your_sex" : "Your sex",
   "day" : "Day",
   "stores_enter" : "For the [BRAND] counters nearest you, please enter your city and state/territory or postal code.",
   "expert_tips_trends.name" : "Expert Tips",
   "enter_city" : "Please enter your City",
   "nav.eyes" : "Eyes",
   "alt.addsamplescheckout_btn.gif" : "Offer Image",
   "replenishment.meta_keywords" : "Estee Lauder, makeup, skincare, fragrance, cosmetics, my account, replenishment service",
   "my_message" : "My Comments:",
   "email_address_starred" : "Email Address",
   "language_1" : "I would like to receive emails in <b>English</b><br>",
   "signin_to_see_cart" : "If you have previously added items to your shopping cart, <a href=\"/account/signin.tmpl\">sign in</a> to see them.",
   "save_results" : "Save Results to Your Profile",
   "order_gift" : "This order is a gift.",
   "view_bag" : "Checkout",
   "checkout_process_header" : "Your Order is processed..",
   "email_discountinued_2" : "Si tiene alguna pregunta, díganoslo y estaremos encantados de ayudarle.",
   "title1" : "Miss",
   "purchase_one_time" : "Purchase this time only",
   "add_new_payment_method" : "Add New Payment Method",
   "help_or_phone_order" : "If you need help or would like to order by phone, call customer service at 1-800-419-4041. Operating hours: M-F xx:xx to xx:xx",
   "other_saved_ship_address" : "Other saved shipping addresses",
   "order_confirm_email_subject" : "Thank you for placing your order at Estee Lauder Online",
   "identical.password2" : "config The password and the verify password fields do not match.",
   "four_Very_Oily" : "4 - Oily Skin",
   "Nav_Sign_Out" : "Sign Out",
   "inside_info_txt" : "Registered members will quickly be delivered deals, samples, and the latest information about new products and styles!",
   "confirmemail" : "Re-type Email Address",
   "pc" : "(PC)",
   "commun_email_alerts" : "I would like to receive email alerts from [BRAND] about new products and exclusive shopping offers.",
   "email_cancelled_1" : "Si tiene alguna pregunta, conteste este email o llámenos al 877-311-3883 si necesita ayuda.",
   "view_order" : "View Order",
   "password_sent_to" : "Your current password will be sent to the email address:",
   "cart.merge" : "Hey, we found some other stuff you didn't buy yet.  Ya gonna pay or what?",
   "payment_option_visa" : "Visa",
   "mobile_alerts" : "MOBILE ALERTS",
   "info_new_products" : "Information about new products and exclusive offers",
   "replenish.error_email.cancellation_warning.subject" : "Please update your Estee Lauder Auto-replenishment order",
   "email_backorder_1" : "Lamentamos informarle que en estos momentos no podemos enviar su orden de reabastecimiento. Este producto(s) no está disponible en este momento. Esperamos poder disponer pronto del producto(s) y enviar su orden de reabastecimiento en cuanto llegue a nuestro centro de distribución.",
   "pls_cur_password" : "Please supply your current password.",
   "mobile_alerts_txt" : "I would like to receive alerts on my mobile phone.",
   "print_complete_order" : "Print Order Details",
   "customer_email_we_received" : "We have received your email and a member of our team will review your inquiry and respond to you as soon as possible.",
   "pin" : "Certificate PIN",
   "Todays_Offer" : "Today's Offer",
   "cert_num" : "Certificate Number (16 digits):",
   "replenishment.service.meta_keywords" : "[BRAND], makeup, skincare, fragrance, cosmetics, my account, replenishment service",
   "canada_ship_charge_txt" : "*Orders shipping to Canada are charged an additional US::currency::",
   "enter_zip" : " Enter zip",
   "billing_information.page_title" : "Billing Information",
   "Delivery_Time_colon" : "Delivery Time:",
   "bronzer" : "bronzer",
   "autoreplen_stat" : "Autoreplenishment Status:",
   "no_dashes_spaces" : "No spaces or dashes",
   "enter_lastname" : "Please enter your Last Name.",
   "edit_payment_method" : "Edit Payment Method",
   "enter_cc" : "Please enter your credit card number with no spaces or dashes",
   "order_review" : "Order Review",
   "email_signup_msg_2" : "special offers and more.",
   "other" : "OTHER",
   "nav.basiccare" : "Basic Care",
   "nav.3stepsets" : "3-Step Sets",
   "First_Name" : "First Name",
   "customer_email_thankyou" : "Thank you for interest in esteelauder.com",
   "customer_name" : "Customer Name",
   "recently_viewed" : "Recently Viewed",
   "store_loc_title" : "Stores & Events > [BRAND]",
   "Delivery_Address" : "Shipping Address:",
   "my_skin_report" : "My Skin Consultation",
   "alt.sign-up_btn.gif" : "Newsletter Subscribe",
   "signin_password" : "Your password is:",
   "order_conf_num" : "Your confirmation number is",
   "pass_reset_txt_p01" : "You recently submitted an inquiry regarding your password. We can confirm that we have the following on record in your [BRAND] Account Profile:",
   "maximum_purchase" : "Maximum Purchase Policy",
   "reset_pw_prompt_txt" : "FOR YOUR SECURITY PLEASE NOTE: At this URL, you will be prompted to RESET YOUR PASSWORD. You will then be able to access your account.",
   "Nav_My_Account" : "My Account",
   "sign_up.page_title" : "Sign Up Now with [BRAND].com",
   "account.address_book.page_title" : "Address Book | [BRAND]",
   "err_enter_address" : "Please enter your address.",
   "password_info" : "Upper and lower case, 6-20 characters",
   "sign_up_confirm.meta_keywords" : "[BRAND], makeup, skincare, fragrance, cosmetics, my account, my [BRAND]",
   "email_thirtydays_4" : "o por teléfono en el 877-311-3883.",
   "nav.shopbystep" : "The 3 Steps",
   "payment_info" : "Billing Information",
   "items" : "[number of items] items in your cart",
   "shipping_and_handling" : "Shipping & Handling",
   "enter_town_district" : "Enter Town or District:",
   "order.status.meta_keywords" : "Your confirmation number is order_conf_txt When your order is shipped, you will receive a shipping ",
   "add_to_favorites" : "Add to Favorites",
   "one_Very_Dry_to_Dry" : "1 - Very Dry to Dry Skin",
   "reset_password_h" : "Reset Password",
   "phone_in_case" : "(In case we need to contact you about your order)",
   "NEW_PASSWORD_VERIFY" : "New passwords do not match. Please re-enter your new password.",
   "skin_type_all_skintypes" : "All Skintypes",
   "NA" : "Not applicable",
   "request" : "Request",
   "create_an_account" : "To create a new account, please complete the following fields:",
   "read_review" : "Read review",
   "pls_confirm_password" : "Please confirm your password.",
   "nav.blush" : "Rouge",
   "details" : "details",
   "customer_email_will_review" : "customer_email_will_review.copy needed for this key",
   "card_type" : "Card Type",
   "no_past_purchases" : "You have no past purchases",
   "no_items" : "There are currently no items in your shopping cart",
   "cl_online_order" : "My Estée Lauder order.",
   "nav.brushcollection" : "Brush Collection",
   "BASIC_REGIMEN" : "BASIC_REGIMEN",
   "mobile" : "(Mobile)",
   "create_pwd_on_next_page" : "(create a password on the next page)",
   "sign_up_email" : "SIGN UP FOR EMAIL",
   "Nav_Items" : "Items",
   "pls_last_name" : "Please enter your surname.",
   "locator_no_results_for" : "No results found",
   "alerts" : "Alerts:",
   "cancel_replenishment" : "Cancel Replenishment",
   "choose_samples" : "Choose Samples",
   "edit_update_shipping" : "Edit/Update Shipping",
   "account.landing.page_title" : "My Account",
   "alt.flag_image_limited_edition" : "LIMITED EDITION",
   "Dry_Combination" : "2 - Dry Combination Skin (dry-combination skin)",
   "po_phone_number" : "Phone Number",
   "expert_tips_trends.trends.name" : "Trends",
   "reenter_password" : "Please re-enter your password",
   "no_address_on_file" : "You have no addresses on file.",
   "fields_are_required" : "Indicates required field",
   "pass_request_email" : "Your current password will be emailed to the address below. You will then be able to use this password to set a new one. Please note, for security reasons, any credit card information you have saved with us will be deleted.",
   "Or_text" : "Or",
   "ship_as_bill" : "Use my shipping as billing",
   "optin_error" : "Sorry, there was an error submitting your request.",
   "main_page_title" : "Estée Lauder Official Site",
   "delivery_every_05" : "Delivery Every 90 Days",
   "nav.3step" : "3-Step",
   "nav.giftwrappings" : "Gift Wrappings",
   "save_message" : "Please save this message for future reference.",
   "account_sign_in.meta_keywords" : "[BRAND], makeup, skincare, fragrance, cosmetics, my account",
   "password_or_reg" : "My [BRAND] Registration or Password",
   "mens" : "Mens",
   "brand_user" : "brand user?",
   "why_buy_estee_lauder_products" : "Why do you buy Estee lauder products",
   "sold_out" : "Sold Out",
   "nav.mens3step" : "Men's 3-Step",
   "cm_opt_anon_status" : "cm_opt_anon_status.copy needed for this key",
   "become_member" : "Become a member of [ BRAND ] Online!",
   "All_Skintypes" : "All Skintypes",
   "mail_magazine" : "About E-mail Newsletter",
   "select" : "Select",
   "po_reset_are_you_sure" : "Are you sure you want to sign out as this customer?",
   "store" : "Store:",
   "shipped" : "shipped",
   "zipcode" : "Zip/Postal Code",
   "set_new_pass.meta_desc" : "Set New Password",
   "cc_number" : "Credit Card Number:",
   "Nav_Need_Help" : "Need Help?",
   "need_help_click_txt" : "If you need help, or order by phone, please contact our customer service at: 0800-9010300.",
   "first_name" : "First Name",
   "chemistry" : "chemistry",
   "shipmethod_option_standard" : "Standard (5-7 Bus days)",
   "out_of_stock" : "Out of stock",
   "bizrate_txt" : "BizRate® 2009 Circle of Excellence",
   "invalid.password" : "config The password you supplied is incorrect.",
   "sign-up_intro_copy" : "You’ll enjoy a more personalized experience with each visit&mdash; and be able to save your custom-fit recommendations, too.",
   "add_new_payment_option_h" : "Add new payment option",
   "passwordhintdesc" : "Enter a question or phrase that will help remind you of your password.",
   "Sign_Up_Mobile_Email" : "Email_Address",
   "dashboard_concern_label3" : "Dark Spots/Uneven Skin Tone",
   "thank_you_for_your_order" : "Thank You for Your Order",
   "replenishment_frequency_120_days" : "Deliver every 120 days",
   "order_number" : "Order Number",
   "promocode" : "Promotional Code",
   "tools" : "Tools",
   "identical.password" : "config The password and the verify password fields do not match.",
   "our_guarantee_viewcart" : "The Estee Lauder Guarantee",
   "returning_customers" : "Returning Customers",
   "requiredor.pc_email_address" : "config Please supply your pc email address or your mobile email address.",
   "customer_type" : "Customer type",
   "options" : "Options",
   "replenishment_item.page_title" : "Replenishment Service Item | Estee Lauder",
   "select_delivery_date" : "Select a delivery day",
   "order_received_paid" : "received, paid",
   "nav.sunprotection" : "Sun Protection",
   "shade_name" : "Shade name:",
   "order_total" : "Order Total:",
   "oct" : "Oct",
   "gender" : "Gender",
   "billing_address_is_shipping" : "Use my delivery address as my billing address",
   "dashboard_concern_header" : "Recommended for your skin concern ",
   "start_personalize_h" : "Start to personalize your [BRAND].com experience!",
   "required.last_name" : "config Please supply your last name.",
   "VIDEO" : "VIDEO",
   "next_ship_date" : "Next ship date",
   "re-enter_password_starred" : "*Re-enter Password",
   "billing_address_h" : "Billing Address",
   "pro_advice" : "Professional Beauty Advice and Makeup Tips",
   "billing" : "Billing",
   "Sign_Up" : "Sign Up",
   "Nav_ESignUp" : "Email Registration",
   "cancel_rejected" : "cancel rejected",
   "Coverage" : "Coverage",
   "create_an_account_h" : "Create an Account",
   "frequency" : "Frequency",
   "new_payment_info.meta_desc" : "New Payment Option",
   "giftcard" : "eGIFT CARD",
   "email_backorder_3" : "Su orden de servicio de reabastecimiento será cargada en su tarjeta de crédito en la fecha de envío.",
   "email_preorder_2" : "El propósito del siguiente email es recordarle que se aproxima la fecha de envío de su orden. Estamos preparando el envío de su orden, programada para salir desde nuestro centro de distribución en 7 días hábiles aproximadamente.",
   "mar" : "Mar",
   "for" : "for:",
   "delete_warn_txt" : "Are you sure you want to delete this address?",
   "skin_tone" : "Skin_T0ne",
   "When_is_your_birthday" : "When is your birthday?",
   "nav.eyeliner" : "Eye Liner",
   "nav.concealer" : "Concealer ",
   "default_address" : "Standard Delivery Address",
   "Select_One" : "Choose one",
   "Phone_requirements" : "Phone <em>one phone number is required</em>",
   "email_sevendays_4" : "para actualizar sus datos del servicio de reabastecimiento en “Mi Cuenta”.",
   "write_review_spanish" : "Escriba un comentario",
   "Pink_Undertone" : "Pink_Undertone",
   "nav.bronzer" : "Bronzer",
   "example_zipcode" : "Example: 1010001",
   "nav.step3moisturize" : "Step 3: Moisturize",
   "po_signin_instructions" : "<b>INSTRUCTIONS</b><br>Enter the customer's email address in the \"Email Address\" field below.<br><br>If customers insist they do not have an email address or they do not want to give it over the phone, please proceed with the following:<br> <li> Ask whether they have ordered on the site (via phone order) before.<br> <li>If they are first-time buyers, please ask for their contact phone number, and enter it in the \"Phone Number\" field below.  Upon submitting, a <u>non-working email address</u> will be created with that phone number for the purposes of creating an account.<br> <li>If they have bought before, they may have provided a phone number, and an account was created with that purchase.  Please ask for the phone number, but try to find that account in .NET (enter the phone number with no spaces and end with a % in the email search field and submit).  It is preferable that the same account is used.  Please find the full email address that was created and enter it in the \"Email Address\" field. If the email address cannot be found, then use the phone number. <i>Note that the \"Phone Number\" field is a last resort.</i><br>",
   "hazmat_note" : "Please note that a signature may be required upon delivery for Overnight or 2nd Day shipments.",
   "Gift_Options_colon" : "Gift Options:",
   "required.password2" : "config Please confirm your password.",
   "undertone" : "not sure of your undertone?",
   "email_alerts_txt" : "I would like to receive alerts from [BRAND] about new products and exclusive offers.",
   "po_currently_signed_in" : "You are currently signed in as",
   "qty" : "Quantity",
   "replenishment_service" : "Replenishment Service",
   "consultant_favorites" : "Consultant Favorites",
   "reg_create_password_h" : "CREATE PASSWORD <span>(<a href='/includes/content/why_create_account.tmpl'  class='overlay-link overlay-height-150 overlay-width-300'>Why do I need to create a password?</a>)</span>",
   "Delivery_Notification" : "Send e-mail delivery is complete",
   "billing_address" : "Billing Address",
   "retake_ff" : "Back to the Foundation Finder",
   "you_have_no_orders" : "You have no orders.",
   "check_bal" : "Check Balance:",
   "address_edit.page_title" : "Edit Address",
   "moisturizers" : "Moisturizer",
   "Shade" : "Shade:",
   "color_finder" : "Color Search",
   "signin_pass_requirements" : "6 to 12 digits, or 6 - 12 characters",
   "subject" : "subject:",
   "skin_type_normal_oily" : "Normal/Oily",
   "replenishment_item.instruction_copy" : "Please note: if you make any changes to your replenishment service, <br/>click the \"Update\" button to be sure your changes take effect.",
   "dashboard_concern_label5" : "Pores",
   "payment_option_debit" : "Debit",
   "Payment" : "Payment",
   "cash_on_delivery" : "Cash on Delivery",
   "hint" : "hint",
   "ship_separately" : "These items will be processed and shipped separately.",
   "shades" : "SHADES",
   "km" : "km",
   "month" : "Month",
   "Delivery_Date" : "Delivery Date",
   "lips" : "Lips",
   "topratedmakeup.scat.href" : "/cms/product/supercat/makeup.tmpl",
   "see_full_page" : "SEE FULL PAGE",
   "Create_an_Account" : "Create an Account",
   "Gift_wrap_restrictions" : "Copy here about gift wrap restrictions, eg no gift wrap on COD items.",
   "customer_service_txt_rightcol" : "Call Customer Service: 877-311-3883",
   "Removing" : "Removing",
   "mobile_colon" : "Mobile:",
   "makeup" : "Makeup",
   "call_us" : "Call Us",
   "your_order_status" : "Your Order Status",
   "giftwrap" : "Gift Wrap:",
   "default_shipping_message_nonshaded" : "Free Standard Shipping with any $50 order",
   "your_billing_address_colon" : "Your Billing Address:",
   "skin_type_very_dry" : "Very Dry",
   "CURRENT_ORDERS" : "CURRENT ORDERS",
   "4_8_numbers_letters" : "4 to 8 digits, or 4 - 8 characters",
   "alt.sign-in_h.gif" : "Sign in",
   "shipping_total_electronic_delivery" : "Electronic Delivery",
   "order_refused" : "Refused / Undelivered",
   "Edit_gift_wrap" : "Change gift options",
   "colour_family" : "Color Family",
   "distinct.pc_email_address" : "config Your pc email address must be different from your mobile email address",
   "in_your_cart" : "in your cart",
   "sign_up_overlay_txt" : "Sign up for a [BRAND].com account to access all of your new personalized features.",
   "Gift_Options_h" : "Gift Options",
   "comments" : "A question choice is required.",
   "no_account" : "config Your account was not found.",
   "order_h" : "Orders",
   "sign_up_confirm_txt_p02" : "You can view and update your account and now start to save any preferences or recommendations to your account for the next time you visit [BRAND].com.",
   "online_order" : "ONLINE ORDER",
   "shipping_information" : "Shipping Information",
   "grooming" : "Grooming",
   "quick_shop" : "Quick shop",
   "replenish.error_email.order_error.subject" : "Your Estee Lauder Auto-replenishment order could not be processed",
   "chars_remaining" : "characters remaining",
   "select_state" : "Please select the state for this address.",
   "expert_tips" : "3XPERT_TIPS",
   "may" : "May",
   "special_events" : "Special Events",
   "reset_pass_txt_p01" : "When you next sign in, you will be prompted to RESET YOUR PASSWORD. Any stored credit card information will have been deleted.",
   "question_12" : "Jobs at Est&#233;e Lauder",
   "ingredient_concerns" : "Concerns About [BRAND] Ingredients",
   "cod" : "COD",
   "payment_options_h" : "Payment Options",
   "order_cancelled" : "Cancelled",
   "gift_message" : "Gift Message:",
   "po_csr_signout" : "csr signout",
   "Offer_Code" : "Offer Code",
   "migrated_user_do_you_accept" : "Do you accept the new legal terms?",
   "view_all" : "View All",
   "last_order_date" : "Last Order Date",
   "replenish.detail.invalid_payment" : "You have no payment method on file for this order.",
   "additionalinfo" : "Checkout Help:",
   "skin_type_normal_combination" : "Normal/Combination",
   "eyes" : "Eyes",
   "lipgloss" : "Lipgloss",
   "order_status_0" : "Pending",
   "sign_up_confirm_txt_p01" : "Begin personalizing your experience by taking our Skin Consultation.",
   "enter_your_email_txt" : "enter your email",
   "nav.skincare" : "Skin Care",
   "brand" : "[BRAND]",
   "edit_shipping_address" : "Edit Shipping address",
   "forgot_pass" : "Forgot your password?",
   "email_preorder_5" : "Si la información anterior no es correcta o si desea cambiar la información de facturación o  envío, comuníquese con nosotros antes de 7 días y con gusto le ayudaremos. Puede ponerse en contacto con un Representante del Servicio al Cliente de Estee Lauder Online llamando al teléfono 877-311-3883 o enviando un email a",
   "address_edit.meta_desc" : "Edit Address",
   "shopping_bag_total" : "Shopping Bag Subtotal",
   "dashboard_greeting" : "You're on your way to great skin!",
   "about_delete_h" : "About To Delete",
   "no_addresses_saved" : "You have no shipping address on file. Please click on \"Make This My Default Shipping Address\" to designate one, or click on the ADD NEW ADDRESS button above.",
   "password_emailed_sub" : "Our records indicate that you have already registered with [BRAND]. Click here to have a temporary password emailed to you.",
   "set_new_pass.page_title" : "Set New Password",
   "address_book_new.meta_desc" : "Address New",
   "question_7" : "Can I obtain an Estée Lauder product that is no longer available?",
   "Colour_Family" : "Color Family",
   "Alphabetically" : "Alphabetical",
   "shipping_instructional_text" : "There are X payment methods available on [BRAND].  Instructions/warnings for Payment Providers and for COD, etc, go here.",
   "buy_frequency" : "Buy Frequency",
   "best_sellers" : "Bestseller",
   "email_thirtydays_2" : "para actualizar su información y estaremos encantados de procesar su próxima orden. Si pasan 30 días, asumiremos que desea cancelar esta orden de reabastecimiento.",
   "sub_total" : "Sub Total:",
   "pores" : "Pores",
   "yes" : "yes",
   "enter_offer_code" : "Enter your Offer Code here:",
   "see_details" : "See Details",
   "customer_service_txt_for_email" : "If you require further assistance, you may contact customer service at 1-800-419-4041.<br><br>Please do not reply to this message. If you have received this message in error, please forward a copy of the entire message, including the headers, to [BRAND EMAIL] so we can correct this matter. We apologize for any inconvenience.",
   "question_5" : "Concerns about an Estée Lauder product",
   "alt.back_home_btn.gif" : "Back to homepage",
   "total_wo_tax" : "Total",
   "on" : "on",
   "postal_code" : "Postal Code:",
   "shop_now" : "<a href=\"/\">Shop Now</a>",
   "account.registration" : "Create an Account | [BRAND]",
   "benefit" : "Benefit",
   "notedesc" : "Include a personalized message with your order. Your message will be printed on a gift card. You may send a message without selecting gift wrap.",
   "order_status_1" : "In Warehouse",
   "terms_conditions_txt" : "I have read, understand and agree to be bound by the <a class=\"overlay-link overlay-height-150 overlay-width-295\" href=\"/includes/content/terms_agreement.tmpl\">Terms and Conditions</a> and <a class=\"overlay-link overlay-height-150 overlay-width-295\" href=\"/includes/content/privacy_policy.tmpl\">Privacy Policy</a> for this website.",
   "select_payment_type" : "Select Payment Type",
   "change" : "change",
   "dashboard_3step_label" : "BASIC REGIMEN",
   "nav.lipstick" : "Lipstick",
   "click_here_add_gift_wrap" : "Click here to add gift wrap",
   "description" : "Description",
   "Payment_Method" : "Payment Method",
   "required.password" : "config Please supply your password.",
   "payment_options" : "Payment Options",
   "order_status_12" : "Cancelled",
   "create_password_h" : "Create Password",
   "address_book_no_default_address_saved" : "You have no default shipping address on file. Please click on \"Make This My Default Shipping Address\" link to designate one, or click on the Add New Shipping Address button above.",
   "return_fulfiller" : "return in progress",
   "terms_and_conditions" : "Terms and Conditions",
   "order_details" : "Order Details",
   "your_confirmation_txt" : "Your Order Number is",
   "write_first_review_spanish" : "Sé el primero en escribir un comentario",
   "escrow_payment" : "Escrow payment",
   "Add_gift_wrap" : "If you wish to add gift wrapping, please click here",
   "account_prefs.page_title" : "Address Book",
   "pls_reenter_new_password" : "Please re-enter your new password.",
   "say_hello" : "[BRAND] - Say Hello to Great Skin - 3-Step Skin Care, Turnaround, Repairwear, Foundation, Lip Gloss, Eye Shadow, Mascara",
   "Your_cart_empty_txt" : "Your cart is currently empty.",
   "alt.get_password_btn.gif" : "Get password",
   "nav.step2exfoliate" : "Step 2: Exfoliate",
   "firstname" : "First Name",
   "replenishment_products_h" : "My Replenishment Products",
   "address_2" : "Address Line 2:",
   "temporary_password" : "Temporary password",
   "giftwrapdesc" : "Add gift wrapping for $3.00",
   "your_default_shipping_address" : "Primary Shipping Address",
   "articles" : "Expert Tips",
   "Skin_Types" : "Skin Types",
   "profile_info" : "Profile Info",
   "show_hint" : "Below is the password hint you saved with us:<br/><span class=\"error\">::PASSWORD_HINT::</span> <br/>If you still cannot remember your password, please <a href=\"javascript:signinSubmit(document.::FORM_NAME::, 'password_request');\">click here</a> and we will email it to you.",
   "security_question" : "Security Question",
   "no_gift_wrapped" : "This order will not be gift wrapped.",
   "replenishment_hd" : "My Auto-Replenishment Items",
   "conf_email-coming" : "We will email you an order confirmation within a few seconds. Once we\\'ve shipped your order, typically within 1-2 business days, we\\'ll send a follow up email.",
   "event" : "Event",
   "questions" : "Questions?",
   "about_you" : "About You",
   "credit_card" : "Credit Card",
   "pls_new_password" : "Please supply your new password.",
   "Delivery_Date_colon" : "Delivery Date:",
   "communications" : "Keep in touch with [BRAND]",
   "newsletter_optin_thanks_desc1" : "Thanks!",
   "account_billing_choose_address" : "This information is not on file. Please click on “Make This My Default Payment Method” to designate one, or click on the Add New Payment Method button above.",
   "there_are" : "There are",
   "ship_type_description" : "Shipping Type:",
   "checkout_samples" : "Congratulations, your order qualifies for 2 free samples!",
   "our_guarantee" : "Our Guarantee",
   "faqlink.text" : "FAQ",
   "required.foo" : "config foo is required",
   "iteminbag" : "There is [#] item in your shopping bag.",
   "skin_shades" : "Shades for your skintone:",
   "nav.powder" : "Powder",
   "nav_address_book" : "Address Book",
   "in_warehouse" : "In Warehouse",
   "question_14" : "Other Concern",
   "your_gc_num" : "Your eGift Card Number:",
   "create_an_account_here" : "To create a new account, <br>enter your email address here.",
   "Change" : "change",
   "choose_and_review_txt" : "In the meantime, if you have questions about your order, please call Customer Service toll-free at 877-311-3883.",
   "question" : "Please enter a comment or message.",
   "err_your_hint" : "Your password hint is:",
   "address_error" : "Please try again.",
   "nav.mens" : "Men's",
   "giftcard_txt" : "A valid credit card is required when using an eGift Certificate. Your credit will not be charged if your order total is less than the balance of your Gift Certificate.<br/><br/>Only one eGift Certificate per order.",
   "subtotal" : "Sub Total",
   "account.orders.meta_keywords" : "[BRAND], makeup, skincare, fragrance, cosmetics, my account",
   "Skin_Tone" : "Skin_T0ne",
   "Confirm_Delete" : "Are you sure you want to delete this address?",
   "nav.fragrance" : "Fragrance",
   "return_policy" : "Return Policy",
   "password" : "Password",
   "required" : "Required",
   "make_default_payment" : "Make this my default Payment Method",
   "signin_example" : "Example: jane@aol.com",
   "nav.selftanners" : "Self Tanners",
   "retype_password" : "Re-type Password",
   "dashboard_concern_label8" : "Dehydration",
   "please_select" : "Please Select",
   "delete" : "Delete",
   "dashboard_concern_label7" : "Radiance",
   "Sign_Up_PC_Email" : "Email:",
   "remove_overlay" : "remove",
   "concealer" : "Concealer",
   "new_payment_info.page_title" : "New Payment Option",
   "no_ship_canada_txt" : "Please note that some items cannot be shipped to Canada. These items have been marked with an (*).",
   "SHADES" : "SHADES",
   "ordercomplete" : "Billing",
   "The_following_added_to_cart_txt" : "The following item has been added to your cart:",
   "newest_info_text" : "Keep up with the latest on esteelauder.com and get:",
   "password_reset_txt" : " \tThe next time you log in using your password, you will be prompted to set a new one. Please note that for security reasons, any credit card information you have saved will be deleted.",
   "BILLING" : "Billing",
   "please_not" : "Please note: At this time we can only accept US billing and shipping addresses.",
   "redness" : "Redness",
   "choose_from_book_txt" : "Or choose an address from your address book",
   "driving_dir_h" : "Driving Directions",
   "shipping_frequency" : "SHIPPING FREQUENCY",
   "make_default_shipping" : "Make this my Primary Shipping Address",
   "err_address_identical" : "The Email Address fields must be identical.",
   "my_account_h" : "My Account",
   "optin_confirm" : "Thank you, your submission has been received",
   "nav.offers" : "Offers",
   "alt.view-cart_btn.gif" : "VIEW CART",
   "works_well_with_tab" : "Works well together",
   "enter_your_email" : "Enter your email, then",
   "enter_address" : "Please enter a valid town or district.",
   "brows" : "Eyebrows",
   "company_name" : "Company Name",
   "account_landing_no_default_address_saved" : "You have no default shipping address on file. Please click on \"Make This My Default Shipping Address\" link to designate one, or click on the Add New Shipping Address button above.",
   "choose_payment_h" : "Choose a payment method from:",
   "Remove" : "Remove from Cart",
   "no_delivery_instructions" : "No delivery instructions.",
   "replenish.error_email.invalid_payment" : "You have no payment method on file for this order.",
   "replenishment_frequency_75_days" : "Deliver every 75 days",
   "email_promotions" : "Yes, I would like to receive alerts from [BRAND] Online about new products and exclusive shopping offers.",
   "orderstatus" : "Order status",
   "forgot_password_txt" : "Forgot Password?",
   "Billing_Address" : "Billing Address",
   "cost" : "Cost",
   "distinct.mobile_email_address" : " \tconfig Your mobile email address must be different from your pc email address",
   "Prefecture" : "Prefecture:",
   "nav.whitening" : "Whitening",
   "returns" : "Returns & Exchanges",
   "email_ordernp_3" : "para actualizar su información de reabastecimiento en la sección Mi Cuenta. También puede llamarnos al 877-311-3883.",
   "faqlink.href" : "/cms/customer_service/faqs.tmpl",
   "ask" : "Ask an Expert",
   "discount" : "Discount:",
   "migrated_user_txt" : "Please note that our Terms and Conditions and Privacy Policy may have changed since you last visited this site.",
   "bestseller" : "Bestsellers",
   "order_is_not_gift_wrapped" : "Order is not gift wrapped",
   "aug" : "Aug",
   "phone_requirements" : "Phone: <span class=\"note\">(in case we need to contact you about your order)</span>",
   "replenish.error_email.out_of_stock" : "Backordered",
   "next_order_date" : "Next Order Date",
   "skin_type_dry" : "Dry",
   "replenish.reminder_email.subject" : "Your  Estee Lauder Auto-Replenishment Reminder",
   "our_guarantee_viewcart_txt" : "If you are not satisfied with your order for any reason, we will be happy to accept a return or exchange.<br>We proudly stand behind every product we sell.",
   "Color_Group" : "Color Group",
   "order_is_gift_wrapped" : "Order is gift wrapped",
   "last_name" : "Last Name",
   "Sign_Up_FName" : "First_Name",
   "gc_nav_buyegiftcard" : "Buy eGift Card",
   "yes_cap" : "YES",
   "return_received" : "Return Received",
   "account_prefs.meta_keywords" : "[BRAND], makeup, skincare, fragrance, cosmetics, my account",
   "nav.shop3step" : "Shop 3-Step",
   "shipmethod_option_electronic_delivery" : "Electronic Delivery",
   "features" : "Features",
   "account_landing_no_default_payment_saved" : "You have no default billing information saved.",
   "password_verify" : "Verify Password:",
   "headtext.overlay" : "<p class=\"gray need-help-mod-sm\"><b>Help?</b>",
   "tax" : "Tax",
   "samples_disclaimer" : "* ALL OFFERS: Quantities are limited and all offers are subject to availablity while supplies last. Only one sample offer may be applied to each order, sample offers may not be combined. To insure best availability, there is a limit of one to a customer. Not all sample offers are available on orders being sent to Canada, contact Customer Service for details. All \"Free Shipping\" offers are for STANDARD SHIPPING only, unless otherwise specified, and do not apply to orders being sent to Canada. Applicable purchase amount does not include shipping, handling or sales tax charges, items noted as not eligible, or items that are not in stock at the time of purchase. Please note expiration dates where applicable. Offers expire at 11:59 PM Eastern Time on the stated expiration date, unless noted. Offers cannot be applied to previously placed orders. We reserve the right to cancel any order due to unauthorized, altered or otherwise ineligible use of offer and to modify or cancel any promotion due to system error or unforseen problems. We reserve the right to substitute any free item offered with an item of equal or greater value. Ongoing offers are subject to change without notice. Other restrictions may apply.",
   "email_sevendays_5" : "Gracias por su atención,",
   "personal_msg" : "Personal Message",
   "add_mobile" : "Add Mobile",
   "two_Dry_Combination" : "2 - Dry Combination Skin (dry skin - mix)",
   "back_to_search" : "Back to Search",
   "gc_nav_abtgiftcards" : "About Gift Cards",
   "question_10" : "Questions about registration/password",
   "customer_service_telephone" : "877-311-3883",
   "expert_tips_trends.trends.href" : "/cms/expert_tips_trends/trends/trend_landing.tmpl",
   "Gift_Invoice" : "Invoice",
   "nav_my_account" : "Account",
   "checkout_billing_no_default_payment_saved" : "You have no default payment Add New Payment Method  on file. Please choose one from the dropdown menu at right, or create a new one by Clicking on Add New Payment Method.",
   "edit_billing_address" : "Edit Billing address",
   "nav.eye_lipcare" : "Eye & Lip Care",
   "nav.mascara" : "Mascara",
   "shipping" : "Shipping",
   "delivery_method_standard" : "Standard: 3-9 Bus Days.",
   "nav.lipliner" : "Lip Liner",
   "loading" : "Loading",
   "new_password_starred" : "* New Password",
   "po_reset_customer" : "Reset Customer",
   "address_book" : "Address Book",
   "no_ship_item_canada_txt" : "This item does not ship to Canada.",
   "replenishment_service_h" : "Replenishment Service",
   "replenish.order_confirm_email.subject" : "Your Estee Lauder Auto-Replenishment Order",
   "billing_address_different" : "Enter a different billing address",
   "Payment_Provider" : "Credit Card (Visa or Mastercard)",
   "personalized_message_txt" : "Include a personalized message to be printed on an Estee Lauder Card when Gift Wrap is selected.",
   "face" : "Face",
   "to" : "To:",
   "group" : "home",
   "pass_request_follow_instructions" : "Follow the instructions in the email to reset your password then access your esteelauder.com account.",
   "reset_pass_txt_p02" : "If you require further assistance, you may contact customer service at 1-800-419-4041.",
   "home" : "Home",
   "Email_Address" : "Email address",
   "YOUR_DEFAULT_SHIPPING_ADDRESS" : "Your default shipping address",
   "enter_new_password_hint" : "Enter your new password hint",
   "payment" : "Payment",
   "forgot_pw" : "Forgot your password? <a id=\"forgot-password\" href=\"javascript:void();\">Click Here</a>.",
   "password_starred" : "*Password",
   "start_addr" : "Please enter your starting street address",
   "integer.birth_month" : "Birth Month",
   "card_number" : "Card Number",
   "COD" : "COD",
   "payment_info.meta_desc" : "Payment Options",
   "po_new_user" : "new user",
   "tos_message.cart_overlay" : "OUT OF STOCK - If you purchase this item you will be notified of the expected ship date via email.",
   "continue_shopping" : "Continue Shopping",
   "coming_soon" : "Coming Soon",
   "pls_choose_password" : "Please choose a password.",
   "registration_update_info" : "Please review your information below and add any missing information.",
   "nav.pores" : "Pores",
   "pass_reset_email_subj" : "Your [BRAND] Online Request",
   "customerservice.landing.href" : "/cms/customer_service/index.tmpl",
   "confirm_delete" : "Are you sure you want to delete this payment method?",
   "new_password_verify" : "New passwords do not match. Please re-enter your new password.",
   "Gift_Invoice_desc" : "Your gift invoice will be sent to the following address:",
   "step1_cleanse" : "Step 1: Cleanse",
   "displaying_products_start" : "Displaying:",
   "Product" : "Product",
   "temp_out_of_stock" : "Out of Stock",
   "forgot_password_h" : "Forgot Password?",
   "total_colon" : "Total :",
   "replenishment_item_h" : "My Replenishment Item",
   "order_status" : "Order Status",
   "ADDRESS_BOOK" : "Address Book",
   "address_as_delivery" : "Use this address as my delivery address.",
   "SHIPPING" : "Shipping",
   "pass_request_instruction" : "Follow the instructions to reset your password by e-mail please call the [BRAND] Online.",
   "replenish.error_email_es.order_error.customer_action" : "Si está interesada(o) en restablecer su servicio de reabastecimiento,  haga clic aquí  para actualizar su información de reabastecimiento en la sección Mi Cuenta. También puede llamarnos al 877-311-3883.",
   "return_to_site" : "You can return to the site and sign in at this location:",
   "newsletters" : "Newsletters:",
   "sign_up_confirm.page_title" : "Sign Up Confirmation",
   "terms_txt" : "By submitting this form, I am acknowledging that I have read, understand, and agree to be bound by the Terms and Conditions and Privacy Policy for this website.",
   "gift_options" : "Gift Note/Gift Wrap",
   "Nav_Item" : "Item",
   "offercode" : "Offer Code",
   "name_title" : "-sama.",
   "other_address" : "Other shipping address",
   "replenish.error_email.cancelled.subject" : "Your Estee Lauder Auto-replenishment order has been cancelled",
   "itemsinbag" : "There are [#] items in your shopping bag.",
   "add_payment_method_h" : "Add Payment Method",
   "product_concerns" : "PRODUCT AVAILABILITY",
   "alt.sign-in_btn.gif" : "Sign in",
   "order_past_purchases" : "Reorder from past purchases",
   "po_session_open_for" : "Session open for",
   "customer_email_greeting_beautyadvice" : "Thank you for taking the time to e-mail us. We want you to know that we have received your e-mail. One of our Online Beauty Advisors will reply to you shortly.",
   "replenish.error_email.cancellation_warning.notification" : "We recently sent an email advising you that we have been unable to process your Estee Lauder Online replenishment order for ::PRODUCT:: .",
   "category" : "Category",
   "replenish.error_email.reminder_error.subject" : "Please update your Estee Lauder Auto-Replenishment information",
   "lastchance.scat.href" : "/products/CATEGORY3893/Last_Chance/index.tmpl",
   "Concern" : "Concern",
   "offer_criteria_not_met" : "You did not meet the criteria for offer code ::offer_code::.",
   "nav_reorder" : "Re-order from Past Purchases",
   "change_pass_text" : "We recently emailed you a temporary password. To keep your personal information secure, please set a new password to access your account.",
   "reviews" : "reviews",
   "billing.info.meta_desc" : "Billing Information",
   "email_thirtydays_3" : "Si tiene alguna pregunta o si necesita ayuda, póngase en contacto con nosotros en",
   "bill_to" : "Bill to:",
   "please_print_page" : "Please print this page as a receipt for your records.",
   "Brand" : "[BRAND]",
   "order_received_unpaid" : "order received, unpaid",
   "account.landing.meta_keywords" : "[BRAND], makeup, skincare, fragrance, cosmetics, my account",
   "interpolation_test" : "config the quick brown ::ANIMAL:: jumped.",
   "billing_address_sp" : "Dirección de Facturación",
   "add_new_address" : "Add New Address",
   "No_Default_Billing_Addresses_Saved" : "Your billing address is not on file.  <a href=\"/account/address_book/index.tmpl\">Click here</a> to add it.",
   "rec_skin_type_iii" : "Recommended Skin Type III",
   "security_question_colon" : "Security Question:",
   "customer_service" : "Customer Service",
   "bank_name" : "Bank Name:",
   "Sort_By" : "Sort by",
   "Form" : "Form",
   "question_8" : "Can you recommend an alternative for a discontinued product or shade?",
   "please_select_shipping_address" : "Please choose an address from the address book at right, or create a new one by clicking on Add New Address.",
   "your_next_delivery" : "Your next delivery: [ORDER NUMBER] on [ORDER DATE]",
   "email_orderconfirm_1" : "Gracias por participar en nuestro servicio de reabastecimiento de Estee Lauder Online. Esperamos que esté disfrutando de la comodidad de recibir automáticamente sus productos favoritos.",
   "prod_name" : "Product Name",
   "shipping_carrier" : "Shipping Carrier",
   "sign_up_confirm.meta_desc" : "Sign Up Confirmation",
   "replenish.error_email.invalid_shipping" : "You have no shipping address on file for this order.",
   "nav.dullness" : "Dullness",
   "err_verify_password" : "Confirm New Password",
   "Neutral_Undertone" : "Neutral_Undertone",
   "FOUNDATION_DESC" : "Stay True Make up<br />Oil Free Formula.",
   "cert_pin" : "Certificate PIN (8 digits):",
   "Sub_Total" : "Sub Total:",
   "order_status_5" : "Backordered",
   "migrated_user_header" : "Legal Acceptance",
   "review" : "review",
   "sign_up_clin_emails" : "Sign me up for [BRAND] emails",
   "last_order" : "Last Order",
   "expert_tips_trends.articles.href" : "/cms/expert_tips_trends/trends/articles_landing.tmpl",
   "order" : "About my Order",
   "see_shades_and_details" : "See Shades and Details",
   "store_loc_bc" : "Store & Event Locator",
   "nav.womens" : "Women's",
   "Bestseller" : "Bestsellers",
   "limited_to_united_states" : "(addresses limited to the United States and Canada)",
   "purchase_this_product" : "PURCHASE THIS PRODUCT &raquo;",
   "tools_gifts" : "Tools & Gifts",
   "password_will_be_sub" : "Your password will be emailed to you.",
   "account.migrated-user.page_title" : "New Legal Notice | [BRAND]",
   "po_become_customer" : "Become Customer",
   "email_backorder_2" : "Para su conveniencia, a continuación encontrará los detalles de su orden de reabastecimiento.",
   "Last_Name" : "Last Name",
   "dashboard_concern_label9" : "Other recommended product",
   "displaying_products_end" : "products",
   "nav.deaging" : "De-Aging",
   "Delivery_Method" : "Dispatch",
   "in_providing_your_email" : "In providing your email address you are agreeing to receive email communication from [Brand] Online.",
   "password_criteria" : "Passwords are case sensitive and must be 6 - 20 characters. Use only letters or numbers (no punctuation or special characters)",
   "max_purchase_policy" : "Maximum Purchase Policy",
   "cartbox.to" : "Gift Shipping",
   "select_cc_type" : "Please select the type of credit card.",
   "search_results" : "Search Results",
   "current_orders_sub" : "Current Orders",
   "one_offer_only" : "Only one offer code can be applied to your purchase",
   "info_please" : "Please take a moment to ensure that we have your most current information, so that we may continue to provide you with the best service possible.",
   "next_auto" : "Next Auto-Replenishment shipment",
   "select_payment_message" : "*Select a payment method:",
   "nav.hand_bodycare" : "Body Care",
   "phone" : "Day Phone :",
   "tracking_number" : "Tracking Number",
   "concerns" : "What are your main skincare concerns?<br>(check your top three concerns)",
   "Add_New_Address" : "Add New Address",
   "pass_request_txt" : "<p>Since you have forgotten your password, we will need to replace it with a new one.</p> <p>Please click submit so your current password will be emailed to the address below. You will then be able to use this password to set a new one. Please note, for security reasons, any credit card information you have saved with us will be deleted.</p>",
   "email_sign_up_intro_copy" : "Sign up now to receive messages from [BRAND] Online! You’ll always be in the know for exciting [BRAND] news.",
   "YOUR_ADDRESS" : "Your Address",
   "change_pass_thanks_txt" : "You are now registered. You are now registered. You are now registered. You are now registered. You are now registered. You are now registered. You are now registered. You are now registered. You are now registered.",
   "select_address_below" : "Select Your Address Below:",
   "Postal_Code" : "Postal Code:",
   "tos_message.cart_item" : "TEMPORARILY OUT OF STOCK - If you purchase this item it will be mailed separately from any other items being purchased today. You will be notified of the expected ship date via email.",
   "starred_last_name" : "*Surname",
   "required.first_name_alternate" : "config Please supply your first name informal.",
   "replenishment_frequency_0_days" : "Purchase One Time",
   "customer_phone" : "Customer Phone Number",
   "choose_other_payment" : "Choose a payment method from:",
   "terms-conditions_nav" : "Terms & Conditions",
   "question_11" : "General questions/feedback on Estée Lauder Website",
   "temp_password" : "Temporary password*",
   "return_in_process" : "return in progress",
   "edit_this_payment_method" : "Edit This Payment Method",
   "Date" : "Date",
   "write_first_review" : "Write the first review",
   "edit_payment_option_h" : "Edit payment option",
   "whats_this" : "Learn more »",
   "replenish.error_email.reminder_error.customer_action" : "If you would be so kind as to contact us with updated information, we will be happy to proceed with the processing of your replenishment order. Simply call us at 877-311-3883 or reply to this email for assistance. Please do not send credit card information via email as it is not a secure method to transmit sensitive personal information.</p><p>You can also <a href=\"https://::SERVERNAME::/account/replenish/\">click here</a> to update your replenishment service information in “My Account”.",
   "your_regimen" : "Products recommended for your skin type:",
   "nav_orders" : "Order Status",
   "password_hint" : "Password Hint (Enter a question or phrase below that will remind you of your password)",
   "po_registered_user" : "registered user",
   "recruiting" : "recruiting.copy needed for this key",
   "nav.aromaticselixir" : "Aromatics Elixir",
   "payment_option_Visa" : "Visa",
   "size" : "Size",
   "email_sevendays_1" : "El propósito del siguiente email es informarle de que en este momento no podemos procesar su orden de reabastecimiento por el siguiente motivo:",
   "do_not_save_credit_card" : "I do not want to save my payment details",
   "signout" : "Sign Out",
   "Female" : "Female",
   "confirm_cancel_replenishment" : "Are you sure you want to cancel your replenishment of this item?",
   "Nav_Sign_Up" : "Sign Up",
   "sign_in_now" : "Sign In",
   "read_review_spanish" : "Leer comentario",
   "faq" : "Frequently Asked Questions.",
   "enter_password" : "Enter Password",
   "write_review" : "Write a review",
   "change_pass_header" : "Password recovery",
   "make_default_cc" : "Save as my default payment option",
   "days" : "days",
   "shipping_method" : "Shipping Method",
   "order_delivered" : "Delivered",
   "po_no" : "no",
   "recent_purch" : "My Recent Purchases",
   "yes_newsletter_txt" : "Sign up for the esteelauder.com newsletter to enjoy the latest looks and launches, plus sample offers and more...",
   "skincare.scat.href" : "/cms/product/supercat/skincare.tmpl",
   "notsure" : "Not Sure",
   "No_Registration_Address_Saved" : "You have no profile address on file. <a href=\"/account/address_book/index.tmpl\">Click here</a> to add it.",
   "inactive" : "Inactive",
   "valid_cc" : "A valid credit card is required when using an eGift Card. If your order total costs less than the balance of your eGift Card, your credit card will not be charged.",
   "The_following_removed_from_cart_txt" : "The following items have been removed from your cart",
   "address_book_address_used_billing" : "You have at least one credit card associated with this address. <a href=\"/account/payment_info/index.tmpl\">Click Here</a> to make changes to your billing information.",
   "yes_mobile_newsletter_txt" : "Yes! I would like to receive a mobile newsletter about products, online offerings, and events.",
   "three_Oily" : "3 - Combination Oily Skin",
   "requiredfields" : "Indicates required field",
   "deaging" : "Anti-Aging",
   "nav.selftan" : "Self Tan",
   "Earliest_possible" : "Earliest available delivery",
   "MORE" : "More",
   "Payment_Method_colon" : "Payment Method:",
   "nav.lipgloss" : "Lip Gloss",
   "rec_skin_type_i" : "Recommended Skin Type I",
   "skin_consultations" : "Skin Consultation",
   "expert_tips_trends.videos.name" : "Videos",
   "delivery_every_02" : "Delivery Every 45 Days",
   "contact_phone" : "Contact Phone Number",
   "product_subtotal" : "Product Subtotal",
   "trends" : "Trends",
   "reg_create_password_popup" : "CREATE PASSWORD",
   "checkout_shipping_no_default_address_saved" : "You have no default Address on file",
   "order_received" : "Received",
   "shippinginfo" : "Shipping & Handling",
   "nav.sets_gifts" : "Sets & Gifts",
   "order_status_4" : "Suspended",
   "FOUNDATION_STRENGTH" : "Neutral Undertone",
   "skin_type_oily" : "Oily",
   "your_email" : "Your email*",
   "customer_email_we_received_consumerservice" : "We are currently experiencing heavier than normal email volumes.  We will respond as quickly as we are able and apologize for any inconvenience this may cause.  If you require a more immediate response, please call us at the toll-free number listed on our website.",
   "egift_check_txt_03" : "eGift Card Number (16 digits)",
   "account.landing.meta_desc" : "My Account",
   "free_shipping_not_met" : "Get Free Standard Shipping: you're just [#] away.",
   "lookup_address" : "Lookup Address",
   "shipmethod_option_second_day" : "Second Day",
   "nav.gifts" : "Gifts & Sets",
   "delivery_every_06" : "Delivery Every 120 Days",
   "order_status_2" : "Shipped",
   "nav.step1cleanse" : "Step 1: Cleanse",
   "shipping_total_second_day" : "Second Day Shipping",
   "your_address" : "Address",
   "required.foo.bar" : "config foobar is required",
   "No_Default_Shipping_Addresses_Saved" : "You have no default address on file. Please choose an address from the address book at right, or create a new one by clicking on Add New Address.",
   "cartbox.message" : "Gift Message",
   "account.registration.page_title" : "Create an Account | Estée Lauder",
   "alt.flag_image_new" : "NEW!",
   "language_2" : "I would like to receive emails in <b>Español</b>",
   "your_sex" : "Your sex",
   "your_security" : "FOR YOUR SECURITY PLEASE NOTE:",
   "name" : "Name:",
   "phone_2" : "Evening Phone:",
   "account_sign_in.page_title" : "Sign In",
   "terms_agreement_and" : " and ",
   "offer_criteria_met" : " \tOffer code ::offer_code:: has been successfully applied.",
   "egift_check_txt_04" : "eGift Card PIN (8 digits)",
   "rec_skin_type_iv" : "Recommended Skin Type IV",
   "purchase_with_egift" : "Purchase with eGift Card",
   "Loading" : "Loading",
   "address_book.page_title" : "Address Book | [BRAND]",
   "pass_sent_txt" : "<p>An email with your password has been sent to the email address below.</p> <p>The next time you sing in using this password, you will be prompted to set a new one. Please note, for security reasons, any credit card information you have saved with us will be deleted.</p>",
   "shipping_total_standard" : "Standard Shipping",
   "Sign_Up_Postal_Code" : "Postal_Code",
   "change_pass_thanks_header" : "You have reset your password",
   "inquiry_regarding" : "Questions",
   "nav.lips" : "Lips",
   "po_phone_order" : "Phone Order",
   "newsletter_optin_thanks_click_account" : "Click here to access your Account.",
   "privacy_policy" : "Privacy Policy",
   "email_signup_msg_1" : "Hear about new products,",
   "Delivery_Time" : "Delivery Time",
   "offers" : "Promotions",
   "complete_form" : "Can't find an answer within the FAQ's above? We'd like to hear from you! Complete and submit the form below.",
   "security_answer_colon" : "Security Answer:",
   "payment_info.meta_keywords" : "[BRAND], makeup, skincare, fragrance, cosmetics, my account, payment",
   "in_your_bag" : "in your shopping bag.",
   "verify_password" : "Verify Your Password",
   "order_not_gift" : "Not a gift order",
   "replenishment_frequency_45_days" : "Deliver every 45 days",
   "Required" : "Required",
   "change_date" : "Change Next Order Date",
   "your_default_payment_info" : "Your default payment information",
   "no_favorites_signin" : "If you are a member of [BRAND] Online and you have saved favorites, <a href=\"/account/index.tmpl\">click here</a> to sign in and see them.",
   "order_status.page_title" : "Order Status",
   "read_reviews" : "Read reviews",
   "nav.sun" : "Sun",
   "lastchance.mpp.href" : "/products/CATEGORY3893/Last_Chance/index.tmpl",
   "err_verify_email" : "Please verify your email address.",
   "order_in_warehouse" : "In Warehouse",
   "account_sign_in.meta_desc" : "Sign In",
   "sign_up_clin_store" : "Yes, I would like to receive email about Gifts with Purchase, In-Store activities and special events.",
   "rec_skin_type_ii" : " Recommended Skin Type II",
   "customer_email_greeting_consumerservice" : "Thank you for taking the time to email us at Estee Lauder Online.  We wanted you to know that we have received your email and that your comments/questions are important to us.",
   "order_num" : "If available please provide your order no.",
   "verify_email_address" : "Verify Email Address",
   "web_site" : "WEBSITE",
   "nav.face" : "Face",
   "email_sevendays_3" : "También puede",
   "email_address" : "Email address",
   "free_shipping_met" : "Congratulations! You qualify for Free Standard Shipping.",
   "nav.concern" : "Concern",
   "newest" : "Newest",
   "Edit" : "Edit",
   "edit_this_address" : "Edit This Address",
   "nav.tools_gifts" : "Tools & Gifts",
   "DESCRIPTION" : "Description",
   "email_preorder_1" : "Gracias por participar en nuestro servicio de reabastecimiento de Estee Lauder Online. Esperamos que esté disfrutando de la comodidad de recibir automáticamente sus productos favoritos.",
   "returning_customers_checkout_signin" : "To sign in, <br/>enter your email address.",
   "all_shades" : "All Shades",
   "replenishment_no_items_info" : "To make any changes to your auto-replenishment items, including shipping address,<br/>billing address, and frequency, please click on \"View/Edit Details\" below.",
   "edit_billing_method" : "Edit Billing method",
   "example_phone" : "",
   "lastname" : "Last Name",
   "close" : "Close",
   "Delivery" : "Shipping",
   "FOUNDATION_TONE" : "Moderately Fair",
   "prods_to_prevent" : "USE THESE PRODUCTS TO PREVENT",
   "dashboard_concern_label4" : "Lines/Wrinkes",
   "Male" : "Male",
   "Colour_Group" : "C0lour_Group",
   "optional_information" : "Optional Information",
   "enter_state" : "Please select the state for this address.",
   "delivery_options" : "Delivery Method",
   "sign_up.meta_keywords" : "[BRAND], makeup, skincare, fragrance, cosmetics, my account, my [BRAND]",
   "thank_you" : "Thank you",
   "residence" : "Residence",
   "message" : "Please limit the length of the giftcard message to 215 characters.",
   "shop" : "Shop",
   "last_ordered" : "Last ordered",
   "integer.birth_day" : "Birth Day",
   "events_local" : "Events at my local [BRAND] Counter(s)",
   "no_new_customer" : "No, I am a new customer.",
   "replenish.error_email.order_error.customer_action" : "If you would like to reinstate your auto-replenishment service, please <a href=\"https://::SERVERNAME::/account/replenish/\">click here</a> to update your auto-replenishment information in the My Account section. You may also call us at 877-311-3883.",
   "deliver_to_different_address" : "Deliver To A Different Address",
   "foundation.name" : "Foundation",
   "make_default_billing" : "Use this address as the billing address.",
   "nav_payment_info" : "Billing Information",
   "Formula" : "Formula",
   "account_landing_no_payment_saved" : "You have no default <br>billing information saved.",
   "no_address_lookup" : "No addresses where found.",
   "Quantity" : "Quantity",
   "sign_up.meta_desc" : "Sign Up Now with [BRAND].com",
   "replenishment.page_title" : "Replenishment Service | Estee Lauder",
   "account_exists" : "An account already exists with that email address.  Please sign in with that email address and update your registration in the \\'my account\\' section.",
   "passwordhint" : "Password Hint",
   "billing_address_header" : "Billing Address",
   "Delivery_Instructions" : "Delivery Instructions",
   "foundation" : "Foundation",
   "nav.foundation" : "Foundation",
   "Sign_In_Forgot" : "Forgot_Password?",
   "no_cap" : "NO",
   "Multiple_tracking_shipping_dates_txt" : "To display more information about this order select the order number. To see the delivery status of your order click on the tracking number. <br/> Multiple tracking numbers for each order indicate different ordering.",
   "gift" : "Gift",
   "invalid.email_address" : " \tconfig this email address is, was, and will forever be in a invalid format.",
   "other_saved_payment" : "Other saved payment options",
   "required.last_name_alternate" : "config Please supply your last name informal.",
   "partner_to_pc_email" : "Insert email type here your registered e-mail address to send you a notification email from the Sagawa Express Shipping",
   "welcome" : "Welcome,",
   "order_suspended" : "Suspended",
   "Delivery_Day" : "Delivery Day",
   "edit" : "Edit",
   "replenishment_index_intro" : "To make any changes to your auto-replenishment items, including shipping address, billing address, and frequency, please click on View/Edit Details",
   "from" : "From:",
   "replenishment_frequency_30_days" : "Deliver every 30 days",
   "MEET_YOUR_MATCH" : "conf Meet Your Match!<br/>Here's the foundation we recommend for your skin.",
   "no_replenishment_item_info" : "You have no auto-replenishment items. <br/>Look for \"Replenishment Service\" while you shop for your favorite products.",
   "Deliver_To" : "Deliver to",
   "order.status.meta_desc" : "Order Status",
   "signin" : "Sign In",
   "required.phone2" : "config Please supply your phone 2.",
   "account.signin.page_title" : "Sign In",
   "invoices_will_be_sent" : "Note: invoices for gifts will be sent to this address.",
   "signin_email_address" : "Your email address is:",
   "refund_completed" : "Refund Completed",
   "prod_questions" : "Product Questions",
   "replen_title" : "My Replenishment Options",
   "edit_address_h" : "Edit address",
   "preorder_message.cart_overlay" : "PRE-ORDER: Order today to reserve yours! As soon as it is available and ready for shipment, we’ll notify you via email.",
   "address_failure" : "Failure!",
   "more" : "More",
   "year" : "Year",
   "expires" : "Expires",
   "question_3" : "How do I take care of my skin?",
   "replenishment.meta_desc" : "Replenishment Service",
   "address_as_delivery_txt" : "Delivery Address",
   "cancel_verified" : "cancel verified",
   "forgot_pass.page_title" : "Forgot Password?",
   "add_to_bag" : "Add to Cart",
   "deliveredforfree" : "*Delivered for free via email",
   "checkout_signup_title" : "Sign Up with [BRAND].com",
   "my_account" : "My Account",
   "alert_prefs" : "Alert preferences",
   "regarding" : "MY QUESTIONS IS REGARDING :",
   "payment_pp" : "Credit Card",
   "thank_you_order_confirm" : "We know there are many brands and stores to choose from, and we appreciate the trust you have put in Estee Lauder, our products and our website. Please feel free to let us know what we can do even better next time.",
   "password_need_asst" : "New password hint*  If you need assistance, please call Customer service at 1-800-419-4041 and we will help you set a new password and password hint.",
   "city_town" : "City/Town",
   "address1" : "Address 1",
   "shipping_address_sp" : "Dirección de Envío:",
   "payment_info.page_title" : "Payment Options",
   "edit_address" : "Edit Address",
   "when_is_birthday" : "Please enter your birth date (required)",
   "terms_agreement_start" : "By submitting this form, I am acknowledging that I have read, understand, and agree to be bound by the ",
   "payment_method_sp" : "Método de Envío",
   "Nav_Sign_In" : "Sign in",
   "giftwrapped" : "The order will be packaged as a gift.",
   "help" : "Help",
   "in_store_service" : "IN-STORE SERVICE",
   "email_preorder_7" : "haga clic aquí",
   "return_to_site_sign_in" : "You can return to the site and reset your password at this location:",
   "email_backorder_4" : "Cuando se envíe su orden, recibirá un email con los datos de envío. Si tiene alguna pregunta entre tanto, conteste este email o llámenos al 877-311-3883. Por favor indique su número de orden cuando se comunique con el Servicio al Cliente de Estee Lauder Online.",
   "works_well_with" : "Works Well With",
   "to_reorder_click_add_btn" : "To re-order click the Add to Bag button.",
   "alt.your_skin_consult" : "Your Skin Consultation",
   "enter_email_checkout_signin" : "To create a new account,<br/>enter your email address:",
   "delivery_every_04" : "Delivery Every 75 Days",
   "sep" : "Sep",
   "back_to_login" : "Back to login",
   "Benefit" : "Benefit",
   "customerservice.landing.name" : "Contact Us"
};
    
        rb.page_data_configuration = {
   "award_seal_awardpage" : 1,
   "catalog.spp.skuFields" : [
      "SKU_ID",
      "SKU_BASE_ID",
      "PRODUCT_ID",
      "PARENT_CAT_ID",
      "SHADENAME",
      "SHADE_DESCRIPTION",
      "SKIN_TYPE",
      "SKIN_TYPE_TEXT",
      "PRODUCT_SIZE",
      "UNIT_SIZE",
      "UOM",
      "DISPLAY_ORDER",
      "STRENGTH",
      "PRICE",
      "formattedPrice",
      "formattedUnitPrice",
      "formattedTaxedPrice",
      "SMOOSH_DESIGN",
      "SMOOSH_PATH_STRING",
      "INVENTORY_STATUS",
      "REFILLABLE",
      "HEX_VALUE",
      "HEX_VALUE_STRING",
      "FINISH",
      "ATTRIBUTE_COLOR_FAMILY",
      "SKIN_TONE",
      "SKIN_TONE_TEXT",
      "PRODUCT_CODE",
      "SKU_IMAGE_1",
      "isShoppable",
      "MISC_FLAG",
      "shippingMessage",
      "worksWith"
   ],
   "globalnav.cart.rpcArgs" : {
      "params" : [
         {
            "order_fields" : [
               "items",
               "samples",
               "address",
               "TRANS_ORDER_ID"
            ],
            "payment_fields" : [
               "address",
               "PAYMENT_TYPE",
               "PAYMENT_AMOUNT",
               "TRANS_PAYMENT_ID"
            ],
            "trans_fields" : [
               "TRANS_ID",
               "payments"
            ]
         }
      ],
      "method" : "trans.get"
   },
   "catalog.mpp.categoryFields" : [
      "CATEGORY_ID",
      "CAT_BASE_ID",
      "products"
   ],
   "catalog.spp.productFields" : [
      "PRODUCT_ID",
      "DEFAULT_CAT_ID",
      "PARENT_CAT_ID",
      "SPECIAL_RESTRICT",
      "TAB_MEDIA",
      "PROD_RGN_NAME",
      "PROD_RGN_SUBHEADING",
      "MISC_FLAG",
      "SUB_LINE",
      "DESCRIPTION",
      "SHORT_DESC",
      "PROD_SKIN_TYPE",
      "PROD_SKIN_TYPE_TEXT",
      "PROD_CAT_IMAGE_NAME",
      "PROD_CAT_DISPLAY_ORDER",
      "SMALL_IMAGE",
      "LARGE_IMAGE",
      "THUMBNAIL_IMAGE",
      "ATTRIBUTE_COVERAGE",
      "ATTRIBUTE_BENEFIT",
      "SKIN_CONCERN_LABEL",
      "XS_HEADER_IMG",
      "tabs",
      "alternateImages",
      "AWARD_1",
      "skus",
      "shaded",
      "sized",
      "url",
      "isShoppable",
      "AVERAGE_RATING",
      "RATING_RANGE",
      "TOTAL_REVIEW_COUNT",
      "ONLY_RATINGS_COUNT",
      "RECOMMENDED_COUNT",
      "RECOMMENDED_PERCENT",
      "RATING_IMAGE",
      "ATTRIBUTE_LABEL_1",
      "ATTRIBUTE_LABEL_2",
      "ATTRIBUTE_LABEL_3",
      "ATTRIBUTE_DESC_1",
      "ATTRIBUTE_DESC_2",
      "ATTRIBUTE_DESC_3",
      "ATTRIBUTE_LABEL",
      "regimenPaths",
      "review_statistics",
      "worksWith"
   ],
   "catalog.mpp.productFields" : [
      "PRODUCT_ID",
      "PROD_BASE_ID",
      "DEFAULT_CAT_ID",
      "SPECIAL_RESTRICT",
      "TAB_MEDIA",
      "PARENT_CAT_ID",
      "PROD_RGN_NAME",
      "PROD_RGN_SUBHEADING",
      "MAIN_ATTRIBUTE",
      "MISC_FLAG",
      "SUB_LINE",
      "DESCRIPTION",
      "SHORT_DESC",
      "PROD_SKIN_TYPE",
      "PROD_SKIN_TYPE_TEXT",
      "PROD_CAT_IMAGE_NAME",
      "PROD_CAT_DISPLAY_ORDER",
      "SMALL_IMAGE",
      "MEDIUM_IMAGE",
      "LARGE_IMAGE",
      "THUMBNAIL_IMAGE",
      "ATTRIBUTE_COVERAGE",
      "ATTRIBUTE_BENEFIT",
      "SKIN_CONCERN_LABEL",
      "PRODUCT_TMPL",
      "priceRange",
      "tabs",
      "alternateImages",
      "AWARD_1",
      "AWARD_2",
      "isShoppable",
      "skus",
      "shaded",
      "sized",
      "url",
      "AVERAGE_RATING",
      "RATING_RANGE",
      "TOTAL_REVIEW_COUNT",
      "ONLY_RATINGS_COUNT",
      "RECOMMENDED_COUNT",
      "RECOMMENDED_PERCENT",
      "RATING_IMAGE",
      "ATTRIBUTE_LABEL_1",
      "ATTRIBUTE_LABEL_2",
      "ATTRIBUTE_LABEL_3",
      "ATTRIBUTE_DESC_1",
      "ATTRIBUTE_DESC_2",
      "ATTRIBUTE_DESC_3",
      "ATTRIBUTE_LABEL",
      "regimenPaths",
      "flagImages",
      "review_statistics",
      "worksWith"
   ],
   "catalog.mpp.skuFields" : [
      "SKU_ID",
      "SKU_BASE_ID",
      "PRODUCT_ID",
      "PARENT_CAT_ID",
      "SHADENAME",
      "SHADE_DESCRIPTION",
      "DISPLAY_ORDER",
      "SKIN_TYPE",
      "SKIN_TYPE_TEXT",
      "PRODUCT_SIZE",
      "UNIT_SIZE",
      "UOM",
      "STRENGTH",
      "PRICE",
      "formattedPrice",
      "formattedUnitPrice",
      "formattedTaxedPrice",
      "INVENTORY_STATUS",
      "SMOOSH_DESIGN",
      "SMOOSH_PATH_STRING",
      "REFILLABLE",
      "HEX_VALUE",
      "HEX_VALUE_STRING",
      "FINISH",
      "ATTRIBUTE_COLOR_FAMILY",
      "LIFE_OF_PRODUCT",
      "SKIN_TONE",
      "PRODUCT_CODE",
      "SKU_IMAGE_1",
      "SKIN_TONE_TEXT",
      "COLORGROUPING",
      "VALUE_TEXT",
      "isShoppable",
      "MISC_FLAG",
      "shippingMessage",
      "worksWith"
   ],
   "catalog.spp.categoryFields" : [
      "CATEGORY_ID",
      "CAT_BASE_ID",
      "CATEGORY_NAME"
   ],
   "group" : "page_data_configuration",
   "globalnav.user.rpcArgs" : {
      "method" : "user.json"
   },
   "user.addresses.rpcArgs" : {
      "method" : "user.addresses"
   },
   "award_seal" : 0
};
    
        rb.checkout = {
   "twopromocode" : "twopromocode",
   "orderreview_h" : "orderreview_h",
   "billing_address" : "billing_address",
   "other_addr_opts" : "other_addr_opts",
   "enter_sec_code" : "enter_sec_code",
   "subtotal" : "subtotal",
   "po_reset" : "po_reset",
   "free_shipping" : "free_shipping",
   "password" : "password",
   "free_shipping_txt" : "free_shipping_txt",
   "verify_email" : "verify_email",
   "NA" : "NA",
   "order_confirmation.page_title" : "order_confirmation.page_title",
   "receipt.meta_desc" : "receipt.meta_desc",
   "shipping_us_only" : "shipping_us_only",
   "taxinfo" : "taxinfo",
   "order.shipped.meta_keywords" : "order.shipped.meta_keywords",
   "retype_password" : "retype_password",
   "bill_to" : "bill_to",
   "shopping_cart.page_title" : "shopping_cart.page_title",
   "purchase_with_egift" : "purchase_with_egift",
   "payment_information" : "payment_information",
   "gift_wrapping" : "gift_wrapping",
   "chars" : "chars",
   "pls_password_hint" : "pls_password_hint",
   "no_items" : "no_items",
   "shopping_cart.meta_desc" : "shopping_cart.meta_desc",
   "po_phone_order" : "po_phone_order",
   "month" : "month",
   "order.email.meta_keywords" : "order.email.meta_keywords",
   "ordercomplete" : "ordercomplete",
   "jan" : "jan",
   "dear" : "dear",
   "alt.verisign.gif" : "alt.verisign.gif",
   "your_order_status" : "your_order_status",
   "please_not" : "please_not",
   "sign_up_email" : "sign_up_email",
   "giftwrap" : "giftwrap",
   "your_next_delivery" : "your_next_delivery",
   "in_your_bag" : "in_your_bag",
   "cust_bottle_ship_txt" : "cust_bottle_ship_txt",
   "po_yes" : "po_yes",
   "pay_method" : "pay_method",
   "order.confirmation.meta_keywords" : "order.confirmation.meta_keywords",
   "thankyou_txt_06" : "thankyou_txt_06",
   "address2" : "address2",
   "edit_update_shipping" : "edit_update_shipping",
   "provide_shipping" : "provide_shipping",
   "order_receipt.page_title" : "order_receipt.page_title",
   "remove" : "remove",
   "enter_address" : "enter_address",
   "enter_phone" : "enter_phone",
   "reenter_password" : "reenter_password",
   "order_review.page_title" : "order_review.page_title",
   "sign_up_clin_store" : "sign_up_clin_store",
   "sign_up_clin_emails" : "sign_up_clin_emails",
   "ship_as_bill" : "ship_as_bill",
   "alt.print_co.gif" : "alt.print_co.gif",
   "jun" : "jun",
   "alt.cnav_6orderreceipt_off.gif" : "alt.cnav_6orderreceipt_off.gif",
   "enter_cc_year" : "enter_cc_year",
   "email_address" : "email_address",
   "salestax" : "salestax",
   "po_email_address" : "po_email_address",
   "select_state" : "select_state",
   "shopping_cart.meta_keywords" : "shopping_cart.meta_keywords",
   "returns" : "returns",
   "may" : "may",
   "enter_email" : "enter_email",
   "total" : "total",
   "po_become_customer" : "po_become_customer",
   "po_csr_signout" : "po_csr_signout",
   "gift_message" : "gift_message",
   "aug" : "aug",
   "shipping.meta_desc" : "shipping.meta_desc",
   "mobile_phone" : "mobile_phone",
   "additionalinfo" : "additionalinfo",
   "lastname" : "lastname",
   "shippingaddress" : "shippingaddress",
   "shipping.meta_keywords" : "shipping.meta_keywords",
   "select_cc_type" : "select_cc_type",
   "one_offer_only" : "one_offer_only",
   "city" : "city",
   "close" : "close",
   "edit_update_billing" : "edit_update_billing",
   "free" : "free",
   "alt.cnav_3shipping_off.gif" : "alt.cnav_3shipping_off.gif",
   "customer_service_txt" : "customer_service_txt",
   "receipt.meta_keywords" : "receipt.meta_keywords",
   "enter_cc_month" : "enter_cc_month",
   "po_start_shopping_as" : "po_start_shopping_as",
   "alt.cnav_6orderreceipt_on.gif" : "alt.cnav_6orderreceipt_on.gif",
   "po_reset_are_you_sure" : "po_reset_are_you_sure",
   "order.shipped.meta_desc" : "order.shipped.meta_desc",
   "enter_state" : "enter_state",
   "check_balance" : "check_balance",
   "your_order_txt" : "your_order_txt",
   "zipcode" : "zipcode",
   "special_offers_h" : "special_offers_h",
   "personal_msg" : "personal_msg",
   "message" : "message",
   "thankyou_txt_04" : "thankyou_txt_04",
   "shipping_instructional_text" : "shipping_instructional_text",
   "cc_number" : "cc_number",
   "one_item" : "one_item",
   "product" : "product",
   "po_place_an_order" : "po_place_an_order",
   "no_items_in_cart" : "no_items_in_cart",
   "feb" : "feb",
   "order_shipped.page_title" : "order_shipped.page_title",
   "choose_other_payment" : "choose_other_payment",
   "thankyou_txt_02" : "thankyou_txt_02",
   "choose" : "choose",
   "on" : "on",
   "you_qualify" : "you_qualify",
   "thankyou_txt_05" : "thankyou_txt_05",
   "add_new_payment_option_h" : "add_new_payment_option_h",
   "shopping_cart_h" : "shopping_cart_h",
   "choose_saved_card" : "choose_saved_card",
   "notedesc" : "notedesc",
   "shipping" : "shipping",
   "passwordhintdesc" : "passwordhintdesc",
   "enter_city" : "enter_city",
   "select_payment_type" : "select_payment_type",
   "promocode" : "promocode",
   "passwordhint" : "passwordhint",
   "alt.addsamplescheckout_btn.gif" : "alt.addsamplescheckout_btn.gif",
   "alt.applycode_btn.gif" : "alt.applycode_btn.gif",
   "signin_to_see_cart" : "signin_to_see_cart",
   "description" : "description",
   "alt.select_btn.gif" : "alt.select_btn.gif",
   "cc_expiration_date" : "cc_expiration_date",
   "po_reset_customer" : "po_reset_customer",
   "view_bag" : "view_bag",
   "whats_this" : "whats_this",
   "viewall" : "viewall",
   "set_as_default_billing" : "set_as_default_billing",
   "estimatedtotal" : "estimatedtotal",
   "ship_to" : "ship_to",
   "accept_terms" : "accept_terms",
   "nov" : "nov",
   "ship_method" : "ship_method",
   "if_mobile_conf" : "if_mobile_conf",
   "confirmemail" : "confirmemail",
   "create_password_h" : "create_password_h",
   "po_registered_user" : "po_registered_user",
   "dec" : "dec",
   "item_shipped_txt" : "item_shipped_txt",
   "edit" : "edit",
   "gift_card" : "gift_card",
   "max_purchasing" : "max_purchasing",
   "us_only" : "us_only",
   "to" : "to",
   "same_as_bill" : "same_as_bill",
   "order_email.page_title" : "order_email.page_title",
   "from" : "from",
   "purchase_to_qualify" : "purchase_to_qualify",
   "alt.clinique_logo.gif" : "alt.clinique_logo.gif",
   "shade_name" : "shade_name",
   "dont_save_cc" : "dont_save_cc",
   "oct" : "oct",
   "cust_bottle_disclaimer" : "cust_bottle_disclaimer",
   "alt.continuecheckout_btn.gif" : "alt.continuecheckout_btn.gif",
   "enter_password" : "enter_password",
   "your_confirmation_txt" : "your_confirmation_txt",
   "item_name" : "item_name",
   "make_default_cc" : "make_default_cc",
   "po_new_user" : "po_new_user",
   "continue_shopping" : "continue_shopping",
   "order.email.meta_desc" : "order.email.meta_desc",
   "po_no" : "po_no",
   "alt.submitorder_btn.gif" : "alt.submitorder_btn.gif",
   "whats_my_sec_code" : "whats_my_sec_code",
   "customerservice" : "customerservice",
   "order.confirmation.meta_desc" : "order.confirmation.meta_desc",
   "print_complete_order" : "print_complete_order",
   "gift_wrap_not" : "gift_wrap_not",
   "year" : "year",
   "pin" : "pin",
   "firstname" : "firstname",
   "po_currently_signed_in_csr" : "po_currently_signed_in_csr",
   "state" : "state",
   "expected_time" : "expected_time",
   "enter_zip" : "enter_zip",
   "revieworder" : "revieworder",
   "deliveredforfree" : "deliveredforfree",
   "order.review.meta_keywords" : "order.review.meta_keywords",
   "look_at_card_back" : "look_at_card_back",
   "jul" : "jul",
   "giftwrapdesc" : "giftwrapdesc",
   "egift_txt" : "egift_txt",
   "have_an_account" : "have_an_account",
   "mar" : "mar",
   "requiredfields" : "requiredfields",
   "enter_lastname" : "enter_lastname",
   "no_dashes_spaces" : "no_dashes_spaces",
   "conf_email-coming" : "conf_email-coming",
   "enter_cc" : "enter_cc",
   "price" : "price",
   "address1" : "address1",
   "cc_charge" : "cc_charge",
   "contact_phone" : "contact_phone",
   "thankyou_txt_01" : "thankyou_txt_01",
   "certificate" : "certificate",
   "review_and_click" : "review_and_click",
   "thankyou_txt_03" : "thankyou_txt_03",
   "there_are" : "there_are",
   "item_shipped_h" : "item_shipped_h",
   "alt.nothankscheckout_btn.gif" : "alt.nothankscheckout_btn.gif",
   "order_complete" : "order_complete",
   "giftwrapped" : "giftwrapped",
   "po_signin_instructions" : "po_signin_instructions",
   "hazmat_note" : "hazmat_note",
   "alt.cnav_4billing_off.gif" : "alt.cnav_4billing_off.gif",
   "offercode" : "offercode",
   "enter_firstname" : "enter_firstname",
   "alt.cnav_5orderreview_off.gif" : "alt.cnav_5orderreview_off.gif",
   "giftcard_redeem" : "giftcard_redeem",
   "works_well_with" : "works_well_with",
   "volume" : "volume",
   "shippinginfo" : "shippinginfo",
   "security_code" : "security_code",
   "quantity" : "quantity",
   "po_currently_signed_in" : "po_currently_signed_in",
   "itemsinbag" : "itemsinbag",
   "qty" : "qty",
   "sign_in_for_fast" : "sign_in_for_fast",
   "replenishment_service" : "replenishment_service",
   "sep" : "sep",
   "alt.checkout_btn.gif" : "alt.checkout_btn.gif",
   "password_info" : "password_info",
   "order.review.meta_desc" : "order.review.meta_desc",
   "order_confirmation_h" : "order_confirmation_h",
   "apr" : "apr",
   "items" : "items"
};
    
        rb.error_messages = {
   "required.state" : "Please select the state for this address.",
   "invalid.password.registration" : "Please enter a valid password with 6-12 alphanumeric characters.",
   "session_timeout" : "You have been signed out due to inactivity. Please sign in again.",
   "integer_invalid.phone1_part1.address" : "Please, provide 3-4 digits in the first field of the Cell Phone.",
   "taken.mobile_email_address" : "The mobile email address you entered is already registered.",
   "required.ship_zip" : "Please enter a postal code.",
   "invalid.verify_email_address.email_us_ba" : "Please enter your verification email address in the following format: jane@aol.com",
   "required.country_id" : "Please select a Country.",
   "signin_error2" : "We do not recognize your sign in information. Please try again. Please note the password field is case sensitive.",
   "invalid.new_password.password_reset" : "Please enter a valid password with 6-12 alphanumeric characters.",
   "signin_replen" : "You have selected auto replenishment. This option requires a [BRAND].com account. It appears that you already have an account with us, <a href=\"/templates/session/checkout_signin.tmpl\">please sign in now.</a>",
   "required.phone" : "Please provide a phone number.",
   "required.pc_eyecolor" : "Please tell us the color of your eyes.",
   "SESSION_TIMEOUT" : "You have been signed out due to inactivity. Please sign in again.",
   "offer_criteria_not_met" : "You did not meet the criteria for offer code <span class=\"offer\">::offer_code::</span>.",
   "unavailable.payment_type.cod.no_cod_to_postal_code" : "We're sorry, but COD is not available at this address.",
   "required.ship_city" : "Please supply the city/town for this address.",
   "no_to_address" : "No to address specified for emailer.",
   "format.phone.contact_us" : "Be sure to use hyphens to separate, as in 877-555-1234. Please don't use any other special characters, including parentheses, periods, etc.",
   "format.password_hint.checkout_registration" : "Please do not enter special characters in your password hint.",
   "required_or.email_address.device_id" : "Please enter your email.",
   "required.pp_payment_type" : "Please, choose the payment method below.",
   "format.shipform_phone2.shipping_~address" : "Please enter a shipping phone number that is 10 to 12 numbers.",
   "error_find_page" : "To see if the page is still available or to find similar information, please try the following:",
   "signedin" : "Please sign in below.",
   "required.shipform_address2.shipping_-address" : "",
   "cart.cost_limit" : "There is a maximum of $750 per order.  Please remove some items from your cart. For details, refer to our <a href=\"/cms/customer_service/shopping_online.tmpl#maxpurchase\">Maximum Purchase Policy</a>.",
   "email_signup_deja_vu" : "To change your email address or unsubscribe to the email newsletter please visit the update page.",
   "length_min.phone1.address" : "Please enter your phone number as 10 to 12 numbers.",
   "interpolation_test" : "config the quick brown ::ANIMAL:: jumped.",
   "required.pc_sunresponse" : "Please tell us how your skin responds to the sun.",
   "format.first_name.registration" : "Incorrectly formatted first name",
   "format.billform_phone1.billing_~address" : "Please enter a billing phone number that is 10 to 12 numbers.",
   "no_addr_found" : "This address is not on file. <a href=/account/address_book/index.tmpl>Click here</a> to visit your address book.",
   "required.billing_address_id.checkout_payment" : "Please provide a billing address",
   "other_payment_method" : "OTHER PAYMENT METHODS",
   "required_or.phone1.phone2" : "Please enter a phone number.",
   "format.billform_phone2.billing_~address" : "Please enter a billing phone number that is 10 to 12 numbers.",
   "account_change_not_requested" : "IMPORTANT: If you did not request this change, or if this request appears to have been made by an unauthorized person, please call Customer Service at 1-800-BRAND (::variable::).",
   "distinct.new_password.password_hint.identical.password_reset" : "Your new password cannot be your password hint. Please try again.",
   "line_item_added" : "::PRODUCTNAME:: in ::PROPERTY:: has been added to your shopping bag.",
   "session_pw_hint" : "Please enter your email address to reset your password.",
   "ccnumber" : "Please supply your credit card number.",
   "identical.password.password2.identical.checkout_registration" : "The passwords you have provided do not match. Please re-enter your password.",
   "required.expires_month.payment" : "Please select an expiration month",
   "format.card_message.checkout_giftmessage" : "We're sorry. A character you have entered is not able to be accepted. Please use only English or Spanish characters for gift messages.",
   "error_page_moved" : "The page you are looking for is no longer available or has been moved.",
   "lenth_min.phone2_part1.address" : "Please, provide 3-4 digits in the first field of the Home Phone.",
   "invalid.email_address2.contact_us" : "Please enter your verify email address in the following format: jane@aol.com",
   "unicode_script.last_name_alternate.user_update" : "Please enter Last Name with katakana.",
   "invalid.pc_email_address" : "Please enter a valid email address",
   "required_and.national_id_1.national_id_2.required_and.checkout_national_id" : "Please, provide the valid National ID.",
   "no_account" : "We do not recognize your sign in information. Please try again. Please note the password field is case sensitive.",
   "account_prod_sold_out" : "This product is sold out and is no longer available online.",
   "cc_deleted" : "Your credit card has been deleted.",
   "comments" : "A question choice is required.",
   "required.billform_postal_code.billing_~address" : "Please supply a billing postal code",
   "required.accepted_privacy_policy.registration" : "Please indicate that you accept the Terms and Conditions and Privacy Policy of the [BRAND] site.",
   "required.user_choice" : "The Contest Entry Field is required.",
   "integer_invalid.phone2_part2.address" : "Please, provide 4 digits in the second field of the Home Phone.",
   "update_acct" : "Your account information is not yet complete. Please update below.",
   "offer_deferred" : "Thank you. We will save the offer code you entered and apply it before you submit your order. Please look for details on the Order Review page.",
   "required.address1.address" : "Please enter a valid address",
   "format.first_name.address" : "Incorrectly formatted first name.",
   "required.zip" : "Please enter your postal code.",
   "restricted.ship_method.pobox" : "Second Day and Overnight Service are not available for orders being sent to APO/FPO addresses or P.O. Box addresses.",
   "msg_hazmat.pobox" : "Please note: Due to the contents of your order, one or more items will be shipped separately and may take up to 10-14 business days to be delivered. Please expect more than one delivery for this order.",
   "cart.cost_limit_with_exceptions" : "There is a maximum of $750 per order.  Please remove some items from your cart. For details, refer to our <a href=\"/cms/customer_service/shopping_online.tmpl#maxpurchase\">Maximum Purchase Policy</a>.",
   "required.ship_address" : "Please supply an address.",
   "invalid.new_user_email_address.signin_new_user" : "Please enter an email address in the following format: jane@aol.com.",
   "required.offer_products_flag" : "The offer code <span class=\"offer\">::offer_code::</span> is",
   "required_and.phone1_area_code.phone1_part1.phone1_part2" : "Please provide your cell phone number.",
   "lenth_min.phone1_part2.address" : "Please, provide 4 digits in the second field of the Cell Phone.",
   "required.shipform_city" : "Please supply a city.",
   "spp_long_out_of_stock" : "This item is currently not available. You will be kept informed via email.",
   "not_registration_offer" : "Sorry, offer code ::OFFER_CODE:: is not for use during registration.",
   "multiple_offer_codes" : "We can only accept two (2) offer codes per order.",
   "account_email_error" : "Please check that the email address in the two fields are identical.",
   "personal_information" : "Personal Information",
   "error_page_not_found" : "Sorry, we can't find that page.",
   "reset_password_current_password_mismatch" : "The password supplied did not match the one in our records.",
   "no_shipping_addresses_saved" : "You have no shipping address on file. Click here to choose or add an address.",
   "cart.no_priced_items" : "You must add at least one product to your shopping cart in order to check out.",
   "identical.new_password" : "The new passwords you have provided do not match. Please re-enter your new password.",
   "acct_notfound" : "Sorry - we could not find your account.",
   "nullpw" : "Tried to set a null password cookie!",
   "signin_optin" : "Our records indicate you have already registered on [BRAND] Online.  Please sign in to opt-in for emails.",
   "survey_over" : "Unfortunately, the survey has ended.",
   "offer_registered_only" : "Sorry, offer code ::OFFER_CODE:: is only available for registered customers.",
   "fss_nonstandard_method" : "This offer code is for Free Standard Shipping.  Please re-enter your offer code and select Standard Service.",
   "success.updated.replenishment" : "Your auto-replenishment has been updated.",
   "length_min.phone1_part2.address" : "Please, provide a valid Home Phone number as 3-4 digits in the first field and 4 digits in the second field.",
   "required.question.contact_us" : "Please select Inquiry.",
   "required.payment_type.checkout_shipping" : "Please select a payment method.",
   "user.query_by_email_address.no_account" : "We do not have a password associated with that email address. Please sign in as a new customer.",
   "required.new_user_email_address" : "To create an account, please enter your email.",
   "no_addresses_saved" : "Your address is not on file. Click here to add it.",
   "format.phone2.address" : "Please enter your mobile number as 10 to 12 numbers.",
   "offer_minimum_purchase_amount" : "Sorry, the total value of your cart does not fulfill the requirements for offer code ::OFFER_CODE::.",
   "session_create_email" : "Please enter an email address to create an account.",
   "required.binary_file_wrong_type" : "The file is the wrong type.",
   "canada_shipmethod_restriction" : "We're sorry, orders destined for Canada are not eligible for 2nd day or Overnight shipping. Standard shipping has been selected for you.",
   "required.current_password" : "Please supply your current password.",
   "required.accept_terms" : "Please accept the Terms and Conditions in order to Create an Account.",
   "mpp_long_out_of_stock" : "This item is currently not available. You will be kept informed via email.",
   "lenth_min.phone1_part1.address" : "Please, provide 3-4 digits in the first field of the Cell Phone.",
   "required.security_question_id" : "Please select a secret question.",
   "pwhint_contains_pw" : "Please use a hint that does not contain your password.",
   "cardmessage_toolong" : "Please limit the length of the gift card message to 280 characters.",
   "required.binary_file" : "A file is required.",
   "required.pc_email_address" : "Please enter your email address.",
   "distinct.pc_email_address.mobile_email_address" : "Your PC email address and your mobile email address must be distinct.",
   "required_and.national_id_1.national_id_2.required_and.national_id" : "Please enter your National/Resident ID.",
   "shipping_upgrade_pomilcan" : "For an order addressed to Air Force Post Office / Fleet Post Office, PO Boxes, Alaska, Hawaii and US Territories, 2 business day delivery service, or next-day delivery service is temporarily unavailable.",
   "required.national_id" : "Please enter your National/Resident ID.",
   "identical.email_address.email_address2.identical.contact_us" : "Please check that the email address in the two fields are identical.",
   "required.pc_oiliness" : "Please tell us about the oiliness or dryness of your skin.",
   "required.card_type.checkout_billing" : "Please select the type of Credit Card.",
   "account_exists_signin" : "Our records indicate that you have an account with that email address. Please enter your password.",
   "cart_merge_notice" : "Please review your order carefully as your shopping bag may contain some items from a previous visit.",
   "required.first_name" : "Please supply a First Name.",
   "cctype_ccnumber_mismatch" : "The credit card number you entered does not match the card type. Please  check that you have entered the correct  card type.",
   "offer_new_registered_only" : "Sorry, offer code ::OFFER_CODE:: is for new registered customers only.",
   "required.postal_code" : "Please enter your postal code.",
   "required.giftwrap" : "To include your personalized message, please click on the box under \\'Gift Wrapping.\\'.",
   "hazmat" : "Please note this product cannot be shipped to a US Military address. Please supply a new shipping address or remove these items from your shopping cart.",
   "emailer_render_failed" : "Emailer failed to render.",
   "unavailable.payment_type.cod.ship_name_ne_reg_name" : "We're sorry, but we are not able to send COD packages to adressees other than yourself.",
   "restricted.ship_method.canada" : "Second Day and Overnight Service are not available for orders going to Canada.",
   "required.security_answer" : "Please enter the answer to your secret question.",
   "required.password" : "Please enter your password.",
   "birth_year" : "Please provide your birth year.",
   "prodqty_sku" : "We're sorry, but we must limit orders to no more than ::QTY:: units of any item. The quantity of ::PRODUCTNAME:: has been reduced for you and added to your shopping bag.",
   "replenishment.correct_errors.future" : "Please supply the missing information below (noted in red) by ::DATE:: in order for us to be able to ship your replenishment order.",
   "account_email_exists" : "Our records indicate that you have an account with that email address. Please enter your password.",
   "required.shipping_address" : "Please enter shipping information below.",
   "required.binary_file_too_large" : "The file is too large.",
   "attempt_to_activate_existing_giftcard_id" : "**Sorry - we could not activate your eGift card because that Gift Card ID already exists.",
   "identical.password2" : "The passwords you have provided do not match. Please re-enter your password.",
   "required.account_prefecture" : "Please select the prefecture for this address.",
   "signin_error" : "Sorry - we cannot sign you in. Either your email address or password is incorrect.",
   "required.billing_address_id.checkout_billing" : "Please supply an address.",
   "giftcard_zero_balance" : "Please note that there is no value left on your gift card, thus it has been removed from your order.",
   "signin__error" : "We do not have a password associated with that email address. Please sign in as a new customer.",
   "required.billform_last_name" : "Please supply a last name.",
   "account_lockout" : "Your account has been locked. To cancel, please contact customer service 0120-950-113.",
   "cart.merge" : "Please review your order carefully as your shopping bag may contain some items from a previous visit.",
   "distinct.password.new_password" : "Your new password cannot be your temporary password. Please enter a different password.",
   "distinct.password" : "Your new password cannot be your password hint. Please try again.",
   "multiset_limit_exceeded" : "You've selected more than the allowed number of sample items. Please select ::variable (sample max):: samples.",
   "cc_mismatch" : "The credit card number you entered does not match the card type. Please  check that you have entered the correct  card type.",
   "identical.new_password.new_password2" : "The new passwords you have provided do not match. Please re-enter your new password.",
   "no_from_address" : "No from address specified for emailer.",
   "csz_s.state" : "Your Address prefecture does not match your city/town and postal code.",
   "required.shipform_address1" : "Please enter a valid address",
   "invalid.postal_code" : "Invalid Postal Code",
   "required.comments.contact_us" : "Please provide additional information.",
   "lenth_min.phone2_part2.address" : "Please, provide 4 digits in the second field of the Home Phone.",
   "cart.empty" : "Your shopping bag cart does not contain priced items.",
   "email_address_verify" : "The Email Address fields must be identical.",
   "sku_unavailable" : ": ::skuname::  is no longer available, and has been removed from your cart.",
   "csz_z" : "Your Address postal code does not match your city/town and prefecture.",
   "invalid_mime_error" : "That file type is not supported.",
   "required.valid_phone" : "Please enter your phone number.",
   "required.address" : "Please enter your address.",
   "required.verify_email_address.email_us_ba" : "Please reenter your email address for verification.",
   "required.password.password_reset" : "Please enter your temporary password.",
   "required.billform_city" : "Please supply a city.",
   "required.accept_privacy" : "Please accept the Privacy Policy in order to Create an Account.",
   "no_shipmethod" : "Please select a shipping method.",
   "length_min.new_password" : "Please supply a new password that is at least 6 characters long.",
   "holiday_newyears_shipping" : "Please Note:  Due to the New Year's Holiday and holiday shipping schedules, orders placed after 3:00 PM EST on Monday, December 31 will be processed on Tuesday, January 2.",
   "user.query_by_email_address.email_address_required" : "Please enter your email address in the following format: jane@aol.com",
   "offer_one_time_use_only_redeemed" : "This offer code has already been redeemed and is no longer valid.",
   "email_taken" : "We cannot change your email address. The email address you selected belongs to an account that already exists in our records.",
   "required.ship_prefecture" : "Please select the prefecture for this address.",
   "required.email_address2.contact_us" : "Please reenter your email address for verification.",
   "integer_invalid.phone2_part1.address" : "Please, provide 3-4 digits in the first field of the Home Phone.",
   "reset_password_not_signed_in_user" : "Please sign in to reset your password.",
   "required.shipform_city.shipping_~address" : "Please supply a shipping city",
   "giftcard_used_for_giftcard_only" : "We're sorry, but you may not use a gift card for gift-card-only orders.",
   "taken.pc_email_address" : "We cannot change your email address. The email address you selected belongs to an account that already exists in our records. ",
   "sku_unavailable_7" : "Unfortunately, ::skuname::  is sold out, and has been removed from your cart.",
   "birth_month" : "Please provide your birth month.",
   "required.card_type.payment" : "Please select a card type",
   "length_min.password.registration" : "Please enter a password 6 or more characters.",
   "email_signup_other_account_exists" : "An account already exists with that email address.  Please select a different email address if you would like to subscribe to the email newsletter.",
   "mpp_sold_out" : "This product is sold out and is no longer available online.",
   "zp_z" : "Your postal code does not match your city/town and prefecture.",
   "required.ship_street" : "Please enter the street/block no.",
   "required.card_number.checkout_payment" : "Please enter a card number",
   "cart_out_of_stock" : "Temporarily out of stock. You may order this product and we will ship it to you as soon as it is back on stock. We will keep you informed via email.",
   "format.card_message_to" : "We're sorry. A character you have entered is not able to be accepted. Please use only English or Spanish characters for gift messages.",
   "full_addr_required" : "Please provide a full address, including a phone number.",
   "update_change_unsubscribe_collistion" : "We're sorry. We cannot allow you to unsubscribe and change the email address at the same time.",
   "general_upload_error" : "Oops, something bad happened on the file upload. Please try again.",
   "required.ship_method" : "Please select a ship method.",
   "offer_group_redeemed" : "This offer code has already been redeemed and is no longer valid.",
   "account_city_error" : "Your Address city/town does not match your zip code.",
   "addr_book_delete" : "That address has been deleted from you Address Book.",
   "signout_msg" : " You have been signed out.",
   "address.packstation_not_allowed_on_billing" : "Packstation not allowed on billing address.",
   "illegal_from_address" : "Email cannot send from address.",
   "required.foo" : "config foo is required",
   "ship_city_error" : "Your Address city/town does not match your prefecture and postal code.",
   "required.billform_address1" : "Please enter a valid address",
   "invalid.password.signin" : "We do not recognize your sign in information. Please try again. Please note the password field is case sensitive.",
   "identical.pc_email_address" : "The Email Addresses should be identical.",
   "required.account_last_name" : "Please enter your Last Name.",
   "account_update_signin" : "After logging on, please update your information.",
   "required.new_password" : "Please supply your new password.",
   "question" : "Comments / Please enter your message.",
   "no_default_payment_address_saved" : "!Please add or choose an address at right.",
   "success.add_sku.collection.repl" : "will be automatically replenished.",
   "length_max.phone2.address" : "Please enter your mobile number as 10 to 12 numbers.",
   "incorrect_pwremind" : "Sorry - we cannot find your account.  Either your password or email address is incorrect.",
   "offer_restrict_country" : "Sorry, offer code ::OFFER_CODE:: is only available in: ::COUNTRY::.",
   "sweepstakes_signin" : "Please sign in to enter the sweepstakes.",
   "collection.signedin_or_recognized.required" : "Please log in below",
   "user.query_by_email_address.email_address_invalid" : "Please enter your email address in the following format: jane@aol.com",
   "addr_cc_deleted" : "Your credit card has been deleted.",
   "offer_ineligible" : "Sorry, you are not eligible to use offer code <span class=\"offer\">::offer_code::</span>. Please see our offers   page for more information.",
   "restricted.ship_method.ak" : "Second Day and Overnight Service are not available for orders going to Alaska.",
   "format.last_name.registration" : "Incorrectly formatted last name",
   "required.house_number" : "The House Number field is required.",
   "required.card_number.checkout_billing" : "Please supply your credit card number.",
   "length_max.phone1.address" : "Please enter your phone number as 10 to 12 numbers.",
   "required.card_type.checkout_payment" : "Please select a card type",
   "format.shipform_postal_code" : "Please enter a 5 digit numerical postal code.",
   "required.ship_address_type" : "Please select the type of address.",
   "required.foo.bar" : "config foobar is required",
   "required.offer_giftwrap" : "Sorry, offer code ::OFFER_CODE:: requires that your order be giftwrapped.",
   "cc_expired" : "The credit card below has expired. Please edit this credit card or add a new one.",
   "offer_criteria_met" : "Offer code <span class=\"offer\">::offer_code::</span> has been successfully applied.",
   "not_allowed.hazmat.address" : "You have products in your cart which may not be sent to an APO address. Please enter a new address, or remove any items below which are marked as not available for delivery to an APO address.",
   "restricted.ship_method.hi" : "Second Day and Overnight Service are not available for orders going to Hawaii.",
   "csz_x.city.state.zip" : "We could not verify your address.",
   "required.password_hint" : "Please supply a password hint.",
   "removed_item.out_of_stock" : "::skuname:: is out of stock and has been removed from your cart.",
   "required_dependency.mobile_email_promotions.mobile_email_address" : "Please enter a mobile email address to receive mobile newsletters.",
   "reset_password_same_password" : "Your new password must be different from your current password.",
   "format.phone2" : "Please enter your mobile number as 10 to 12 numbers.",
   "length_min.phone1_part1.address" : "Please, provide a valid Home Phone number as 3-4 digits in the first field and 4 digits in the second field.",
   "required.checkout_email_address_verify" : "The Email Address fields must be identical.",
   "day_telephone" : "*Day Telephone",
   "fu2_nonstandard_method" : "You entered an offer code for a shipping upgrade, but you did not select standard shipping.",
   "format.postal_code.address" : "Please enter a 5 digit numerical postal code.",
   "migrated.mobile_account.signin" : "If you do not have a password (previous mobile users, etc), please create an account.",
   "required.account_first_name" : "Please enter your First Name.",
   "required.phone.contact_us" : "Please provide a phone number.",
   "select_a_giftcard" : "Please select a Gift Card Value.",
   "required.city" : "Please supply the city for this address.",
   "account_prod_discontinued" : "This product has been discontinued and is no longer available online.  We recommend ::variable::.",
   "offer_fu2_used_overnight" : "This offer code is for Free 2nd Day Shipping.  Please re-enter your offer code and select 2nd Day Service.",
   "required.pc_lines" : "Please tell us about your skins surface lines.",
   "account_postalcode_error" : "Your Address zip code does not match your city/town.",
   "please_signin" : "Please sign in below.",
   "format.national_id_1.national_id_2.checkout_national_id" : "Please, provide the valid National ID.",
   "invalid.password2.registration" : "Please enter a password hint that is 6-12 characters.",
   "length_min.password2.registration" : "Please enter a password 6 or more characters.",
   "spp_out_of_stock" : "Temporarily out of stock. You may order this product and we will ship it to you as soon as it is back on stock. We will keep you informed via email.",
   "badqty" : "Please enter a number into the quantity box to purchase an item.",
   "holiday_xmas_shipping" : "Please Note:  Due to the Christmas Holiday and holiday shipping schedules, orders placed on Monday, December 24 will be processed on Wednesday, December 26. Our Customer Service Center will close at 5:00 PM EST on December 24 and will reopen at 8:00 AM EST on December 26 to allow our staff to spend the holiday with their families. Happy Holidays!",
   "lostpw_retrieved_msg" : "We have sent your password to your email address.",
   "not_checkout_offer" : "Sorry, offer code ::OFFER_CODE:: is not for use at checkout.",
   "no_default_shipping_select_address" : "You have no default shipping address. Please select or add a shipping address.",
   "required.college_name" : "A College Name is required.",
   "required.pc_email_address2" : "Please re-enter your email address.",
   "nonunique_shipoffer" : "Please note only one offer code can be accepted per order.",
   "required.expires_year.payment" : "Please select an expiration year",
   "identical.pc_email_address2" : "The Email Addresses should be identical.",
   "distinct.new_password.password_hint" : "Your new password cannot be your password hint. Please try again.",
   "email_signup_email_one_type_required" : "You must indicate at least one choice of email you would like to receive.",
   "format.postal_code" : "Please enter a 5 digit numerical postal code.",
   "removed_item" : "::skuname:: has been removed from your cart.",
   "required.phone1" : "Please provide a phone number.",
   "length_min.phone2_part2.address" : "Please, provide a valid Cell Phone number as 3-4 digits in the first field and 4 digits in the second field.",
   "length_min.phone2_part1.address" : "Please, provide a valid Cell Phone number as 3-4 digits in the first field and 4 digits in the second field.",
   "account_prefecture_error" : "Your Address prefecture does not match your city/town and postal code.",
   "required.account_zip" : "Please enter a zip code.",
   "required_dependency.pc_email_promotions.pc_email_address.required_dependency.registration" : "Please enter email address to signup for emails and special offers",
   "email_only_acct_found" : "This feature is only provided to registered members.",
   "gift_card_value" : "Please select a gift card value.",
   "required.card_number.payment" : "Please provide your credit card number",
   "required..zip_for_event" : "Please provide your zip code so we may notify you of [BRAND] events in your area.",
   "required.shipform_address2" : "Please supply a street name.",
   "required.billform_title.billing_~address" : "Please supply a billing title",
   "required.address2.address" : "Please supply your Street Name.",
   "required.comment.email_us_ba" : "Please provide additional information.",
   "required.pc_email_address.registration" : "Email address is required",
   "distinct.password.password_hint" : "Please enter a password hint that is different from your password.",
   "identical.pc_email_address.pc_email_address2" : "The Email Address fields must be identical.",
   "prodqty" : "The product quantity for an item in your Shopping Bag has exceeded our Maximum Purchasing Policy. The product quantity is now adjusted to meet our policy maximum. For details, refer to our Maximum Purchasing Policy.",
   "national_id_exists..national_id" : "The account with this National ID already exists.",
   "required.pc_haircolor" : "Please tell us the color of your hair.",
   "bad_cc_number" : "The credit card number you entered is not valid. Please  check that you have entered the correct  number.",
   "invalid_offer_code" : "The offer code <span class=\"offer\">::offer_code::</span> is not valid.",
   "required.billing_address_id.payment" : "Please select an address.",
   "us_addr_not_country" : "You entered a US address, but did not select US as your country.",
   "required.pc_poresize" : "Please tell us your pore size.",
   "required.pc_breakout" : "Please tell us your breakout tendency.",
   "format.shipform_phone1.shipping_~address" : "Please enter a shipping phone number that is 10 to 12 numbers.",
   "required.gc_giftcard_number.gc_giftcard_pin" : "Invalid card number and pin combination.",
   "cart_long_out_of_stock" : "Unfortunately, products that are temporarily out of stock at the time of purchase are not eligible for promotional offers.",
   "email_update_account_not_found" : "Sorry - we could not find a record matching the information you provided.",
   "required.billing_address_id" : "Please provide a billing address",
   "required.billform_address2" : "Please supply a street name.",
   "format.national_id_1.national_id_2.national_id" : "Please, provide the valid National ID.",
   "identical.new_password.new_password_verify.identical.password_reset" : "The new passwords you have provided do not match. Please re-enter your new password.",
   "delivery_options" : "Delivery Method",
   "incorrect.security_answer" : "Wrong answer your secret question. Please enter the correct details.",
   "spp_sold_out" : "This product is sold out and is no longer available online.",
   "required.billform_first_name" : "Please supply a first name.",
   "message" : "Please limit the length of the giftcard message to 215 characters.",
   "required.email_address" : "Please supply your email address.",
   "unable_to_retrieve_egiftcard" : "We are currently unable to retrieve your eGift Card. Please contact a customer service representative.",
   "required.shipform_last_name" : "Please supply a last name.",
   "addr_book_saved_changes" : "The changes you made have been saved.",
   "format.phone1" : "Please enter a phone number that is 10-12 numbers and does not start with a 0 or 1.",
   "account_long_out_of_stock" : "This item is currently not available. You will be kept informed via email.",
   "offer_minimum_purchase_items" : "The minimum requirement for redeeming this offer code has not yet been met.",
   "no_shipping_info" : "Please provide your shipping information below.",
   "invalid.password" : "Please enter a valid password with 6-12 alphanumeric characters.",
   "required.new_password2" : "Please re-enter your new password.",
   "offer_sample_out_of_stock" : "Sorry, ::skuname:: provided by offer code <span class=\"offer\">::offer_code::</span> is no longer   available.",
   "length_min.phone2.address" : "Please enter your mobile number as 10 to 12 numbers.",
   "sku_already_in_collection.add_sku.collection" : "was already added to your favorites.",
   "restricted.ship_method.us_territory" : "Second Day and Overnight Service are not available for orders going to US Territories.",
   "cart.qty_limit" : "The product quantity for an item in your Shopping Bag has exceeded our Maximum Purchasing Policy. The product quantity is now adjusted to meet our policy maximum. For details, refer to our <a href=\"/cms/customer_service/shopping_online.tmpl#maxpurchase\" target=\"_blank\">Maximum Purchasing Policy</a>.",
   "required.billform_last_name.billing_~address" : "Please supply a billing last name",
   "signin_update" : "You must be signed in to update your registration.",
   "invalid.card_number" : "Invalid card number",
   "account_exists" : "Our records indicate that you have an account with that email address. Please enter your password. ",
   "mpp_out_of_stock" : "Temporarily out of stock.  You may order this product and we will ship it to you as soon as it is back on stock. We will keep you informed via email.",
   "required.billform_address1.billing_~address" : "Please supply a billing street name",
   "length_min.password" : "Please supply a password that is at least 6 characters long.",
   "mismatch.card_type.card_number" : "The number provided does not match card type",
   "mpp_discontinued" : "This product has been discontinued and is no longer available online.",
   "identical.password" : "The passwords you have provided do not match. Please re-enter your password.",
   "cart_limit" : "There is a maximum of $XXX per order.  Please remove some items from your cart. For details, refer to our <a href=\"#\" onClick=\"popWin('/customerservice/cs_max_popup.tmpl', 'max', '516', '250', 'yes')\">Maximum Purchasing Policy</a>.",
   "required.billform_city.billing_~address" : "Please supply a billing city",
   "required.full_name" : "Please enter your name.",
   "account_signin_to_edit" : "Please sign in to update your registration profile.",
   "order_review_problem" : "Please review your order and correct any problems.",
   "expired_offer_code" : "Offer code  <span class=\"offer\">::offer_code::</span>  has expired.",
   "required.expires_month" : "Please supply the month when your credit card expires.",
   "invalid.email_address" : "Please enter your email address in the following format: jane@aol.com",
   "ship_postalcode_error" : "Your Address postal code does not match your city/town and prefecture.",
   "sku_unavailable_now" : "This item is currently not available. You will be kept informed via email. This item will be available in ::variable::",
   "requiredor.pc_email_address" : "config Please supply your pc email address or your mobile email address.",
   "required.title" : "Please select your title.",
   "one_time_use.offer_criteria_not_met" : "The offer code <span class=\"offer\">::offer_code::</span> has already been redeemed and is no   longer valid.",
   "format.password_hint.registration" : "Please do not enter special characters in your password hint.",
   "signin_modaddr" : "You must be signed in to modify your address book.",
   "addr_book_confirm_delete" : "Are you sure you want to delete this address? Yes | No",
   "no_cc_info" : "Please provide your payment information below.",
   "cc_type_id" : "Please select the type of Credit Card.",
   "required.email_address2" : "Please re-enter your email address for verification.",
   "identical.password.password2" : "The passwords you have provided do not match. Please re-enter your password.",
   "required.account_address" : "Please enter your address.",
   "csz_x" : "We could not validate your address.",
   "format.card_message_from" : "We're sorry. A character you have entered is not able to be accepted. Please use only English or Spanish characters for gift messages.",
   "required.expires_year" : "Please supply the year when your credit card expires.",
   "empty_cart" : "Cart contains no priced items.",
   "required.phone2" : "config Please supply your phone 2.",
   "required.last_name" : "Please supply a Last Name. ",
   "addr_deleted" : "That address has been deleted from your address book.",
   "required.shipform_first_name" : "Please supply a first name.",
   "birth_day" : "Please provide your birth day.",
   "required.billform_first_name.billing_~address" : "Please supply a billing first name",
   "format.last_name.address" : "Incorrectly formatted last name.",
   "new_password_verify" : "The new passwords you have provided do not match. Please re-enter your new password.",
   "offer_cc_type_id" : "Sorry, offer code ::OFFER_CODE:: is limited to ::REQUIRED_CARD:: customers.",
   "invalid.new_user_email_address" : "Please enter your email address in the following format: jane@aol.com",
   "required.billform_address2.billing_~address" : "Please supply a billing house number",
   "password_tooshort" : "Please supply a password that is at least 6 characters long.",
   "select_a_prod" : "Please select a product size below.",
   "account_prod_out_of_stock" : "Temporarily out of stock.  You may order this product and we will ship it to you as soon as it is back on stock. We will keep you informed via email.",
   "ship_prefecture_error" : "Your Address prefecture does not match your city/town and postal code.",
   "offer_one_time_use_only_ineligible" : "XXX promotion code is not available. For more information, please visit our discount page.",
   "required_or.pc_email_address.mobile_email_address" : "Please enter either a PC email address or a mobile email address.",
   "ecard_recipient_info" : "You must provide complete information for all ecard recipients.",
   "incomplete_address" : "Please supply an address.",
   "integer_invalid.phone1_part2.address" : "Please, provide 4 digits in the second field of the Cell Phone.",
   "offer_message_fos" : "You need to spend ::minimum_purchase_needed:: in order to receive free overnight shipping.",
   "max_char_exceeded" : "You have exceeded the maximum number of characters.",
   "required.account_city" : "Please enter the city/town for this address.",
   "update_signup_email_updating_found_email" : "We cannot change your email address. The email address you selected belongs to an account that already exists in our records.",
   "identical.new_password.new_password_verify.identical.change_password" : "Please enter a valid password using 6-12 alphanumeric characters.",
   "invalid_zip.zip" : "Invalid Postal Code",
   "format.billform_postal_code" : "Please enter a 5 digit numerical postal code.",
   "invalid_email" : "Please enter an email address in the following format: jane@aol.com.",
   "success.add_sku.collection" : "was added to your favorites.",
   "no_qty" : "Please enter a number into the quantity box to purchase an item.",
   "account_email_updated" : "Thanks for visiting BRAND.COM.  Per your request, the e-mail address associated with your account has been changed. The old address was ::variable::. The new address is ::variable::.",
   "invalid.mobile_email_address.registration" : "Please enter a valid mobile email address.",
   "spp_discontinued" : "This product has been discontinued and is no longer available online.",
   "csz_c" : "Your Address city/town does not match your prefecture and postal code.",
   "not_allowed.ca_restricted_skus.address" : "Please note that some items cannot be shipped to Canada. These items have been marked with an (*).",
   "invalid_password" : "Please re-enter your temporary password.  Please note the password field is case sensitive.",
   "offer_one_time_use_only" : "This offer code has already been redeemed and is no longer valid.",
   "account_updated" : "Your personal information has been updated",
   "required.pc_skincolor" : "Please tell us your skin color.",
   "csz_s" : "Your Address prefecture does not match your city/town and postal code.",
   "removed_item.sold_out" : "::skuname:: is sold out and has been removed from your cart.",
   "restricted.ship_method.military" : "Second Day and Overnight Service are not available for orders being sent to APO/FPO addresses or P.O. Box addresses.",
   "offer_giftcard_required" : "The ::OFFER_CODE:: offer is valid only when redeeming a [BRAND] gift card.  Please enter a gift card number and pin in order to use this offer.",
   "replenishment.correct_errors.past" : "Please supply the missing information below (noted in red) by ::DATE:: in order to reinstate your replenishment order.",
   "required.password2" : "Please re-enter your password for verification.",
   "identical.email_address.email_address2" : "The email addresses you have provided do not match. Please re-enter your  email address.",
   "requiredor.mobile_email_address" : "config Please supply your mobile email address or your pc email address.",
   "required.ship_phone" : "Please provide a phone numbe",
   "required.new_password_verify" : "Please re-enter your new password.",
   "distinct.password_hint.password.identical.registration" : "Please enter a password hint that is different from your password.",
   "giftcard_error" : "There is a problem with your Gift Card info. Please see below.",
   "offer_code_not_started" : "Offer code  <span class=\"offer\">::offer_code::</span>  is not yet valid."
};
    /*  Prototype JavaScript framework, version 1.6.0.3
 *  (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.3',

  Browser: {
    IE:     !!(window.attachEvent &&
      navigator.userAgent.indexOf('Opera') === -1),
    Opera:  navigator.userAgent.indexOf('Opera') > -1,
    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,
    SelectorsAPI: !!document.querySelector,
    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 = (function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method);

        value.valueOf = method.valueOf.bind(method);
        value.toString = method.toString.bind(method);
      }
      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]
      .replace(/\s+/g, '').split(',');
    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);
  },

  defer: function() {
    var args = [0.01].concat($A(arguments));
    return this.delay.apply(this, args);
  },

  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)));
    };
  }
});

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.stripTags().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('')
});

String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.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;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    var index = -number, slices = [], array = this.toArray();
    if (number < 1) return array;
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator.call(context, value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator.call(context, value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator.call(context, value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    var result;
    this.each(function(value, index) {
      if (iterator.call(context, value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator.call(context, 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) {
    this.each(function(value, index) {
      memo = iterator.call(context, 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 || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator.call(context, 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) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    return this.map(function(value, index) {
      return {
        value: value,
        criteria: iterator.call(context, 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 [];
    // In Safari, only use the `toArray` method if it's not a NodeList.
    // A NodeList is a function, has an function `item` property, and a numeric
    // `length` property. Adapted from Google Doctype.
    if (!(typeof iterable === 'function' && typeof iterable.length ===
        'number' && typeof iterable.item === 'function') && 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, context) {
    $R(0, this, true).each(iterator, context);
    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) {
      // simulating poorly supported hasOwnProperty
      if (this._object[key] !== Object.prototype[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.inject([], function(results, pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return results.concat(values.map(toQueryPair.curry(key)));
        } else results.push(toQueryPair(key, values));
        return results;
      }).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 || { });
  if (element) this.Element.prototype = element.prototype;
}).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 = $(element);
    element.style.display = 'none';
    return element;
  },

  show: function(element) {
    element = $(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(element, 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);

    if (element.compareDocumentPosition)
      return (element.compareDocumentPosition(ancestor) & 8) === 8;

    if (ancestor.contains)
      return ancestor.contains(element) && ancestor !== element;

    while (element = element.parentNode)
      if (element == ancestor) 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 || value == 'auto') {
      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 (Prototype.Browser.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.toUpperCase() == '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 element;
    // 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 element;
    // 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 && (element.tagName.toUpperCase() == '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);
      // IE throws an error if element is not in document
      try { element.offsetParent }
      catch(e) { return $(document.body) }
      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);
        try { element.offsetParent }
        catch(e) { return Element._returnOffset(0,0) }
        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.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
    function(proceed, element) {
      try { element.offsetParent }
      catch(e) { return Element._returnOffset(0,0) }
      return proceed(element);
    }
  );

  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 frameBorder').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.toUpperCase() == '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.toUpperCase(), 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 = { }, B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      if (B.WebKit && !document.evaluate) {
        // Safari <3.0 needs self.innerWidth/Height
        dimensions[d] = self['inner' + D];
      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
        // Opera <9.5 needs document.body.clientWidth/Height
        dimensions[d] = document.body['client' + D]
      } else {
        dimensions[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();

    if (this.shouldUseSelectorsAPI()) {
      this.mode = 'selectorsAPI';
    } else if (this.shouldUseXPath()) {
      this.mode = 'xpath';
      this.compileXPathMatcher();
    } else {
      this.mode = "normal";
      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(e))
      return false;

    return true;
  },

  shouldUseSelectorsAPI: function() {
    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;

    if (!Selector._div) Selector._div = new Element('div');

    // Make sure the browser treats the selector as valid. Test on an
    // isolated element to minimize cost of this check.
    try {
      Selector._div.querySelector(this.expression);
    } catch(e) {
      return false;
    }

    return true;
  },

  compileMatcher: function() {
    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;
    var e = this.expression, results;

    switch (this.mode) {
      case 'selectorsAPI':
        // querySelectorAll queries document-wide, then filters to descendants
        // of the context element. That's not what we want.
        // Add an explicit context to the selector if necessary.
        if (root !== document) {
          var oldId = root.id, id = $(root).identify();
          e = "#" + id + " " + e;
        }

        results = $A(root.querySelectorAll(e)).map(Element.extend);
        root.id = oldId;

        return results;
      case 'xpath':
        return document._getElementsByXPath(this.xpath, root);
      default:
       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)]",
      'checked':     "[@checked]",
      'disabled':    "[(@disabled) and (@type!='hidden')]",
      'enabled':     "[not(@disabled) and (@type!='hidden')]",
      '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]+:)?[\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) 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 && (!node.type || node.type !== 'hidden'))
          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 == v || nv && nv.startsWith(v); },
    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
    '*=': function(nv, v) { return nv == v || nv && nv.include(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 != 'file' && (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.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, value) {
    if (Object.isUndefined(value))
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, currentValue, single = !Object.isArray(value);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        currentValue = this.optionValue(opt);
        if (single) {
          if (currentValue == value) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = value.include(currentValue);
      }
    }
  },

  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) {
      event = Event.extend(event);

      var node          = event.target,
          type          = event.type,
          currentTarget = event.currentTarget;

      if (currentTarget && currentTarget.tagName) {
        // Firefox screws up the "click" event when moving between radio buttons
        // via arrow keys. It also screws up the "load" and "error" events on images,
        // reporting the document as the target instead of the original image.
        if (type === 'load' || type === 'error' ||
          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
            && currentTarget.type === 'radio'))
              node = currentTarget;
      }
      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
      return Element.extend(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) {
      var docElement = document.documentElement,
      body = document.body || { scrollLeft: 0, scrollTop: 0 };
      return {
        x: event.pageX || (event.clientX +
          (docElement.scrollLeft || body.scrollLeft) -
          (docElement.clientLeft || 0)),
        y: event.pageY || (event.clientY +
          (docElement.scrollTop || body.scrollTop) -
          (docElement.clientTop || 0))
      };
    },

    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;
  }


  // Internet Explorer needs to remove event handlers on page unload
  // in order to avoid memory leaks.
  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  // Safari has a dummy event handler on page unload so that it won't
  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
  // object when page is returned to via the back button using its bfcache.
  if (Prototype.Browser.WebKit) {
    window.addEventListener('unload', Prototype.emptyFunction, false);
  }

  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();// script.aculo.us scriptaculous.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.8.2',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
  },
  REQUIRED_PROTOTYPE: '1.6.0.3',
  load: function() {
    function convertVersionString(versionString) {
      var v = versionString.replace(/_.*|\./g, '');
      v = parseInt(v + '0'.times(4-v.length));
      return versionString.indexOf('_') > -1 ? v-1 : v;
    }

    if((typeof Prototype=='undefined') ||
       (typeof Element == 'undefined') ||
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) <
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);

    var js = /scriptaculous\.js(\?.*)?$/;
    $$('head script[src]').findAll(function(s) {
      return s.src.match(js);
    }).each(function(s) {
      var path = s.src.replace(js, ''),
      includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
};

Scriptaculous.load();// script.aculo.us builder.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Builder = {
  NODEMAP: {
    AREA: 'map',
    CAPTION: 'table',
    COL: 'table',
    COLGROUP: 'table',
    LEGEND: 'fieldset',
    OPTGROUP: 'select',
    OPTION: 'select',
    PARAM: 'object',
    TBODY: 'table',
    TD: 'table',
    TFOOT: 'table',
    TH: 'table',
    THEAD: 'table',
    TR: 'table'
  },
  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  //       due to a Firefox bug
  node: function(elementName) {
    elementName = elementName.toUpperCase();

    // try innerHTML approach
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;

    // see if browser added wrapping tags
    if(element && (element.tagName.toUpperCase() != elementName))
      element = element.getElementsByTagName(elementName)[0];

    // fallback to createElement approach
    if(!element) element = document.createElement(elementName);

    // abort if nothing could be created
    if(!element) return;

    // attributes (or text)
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array) ||
        arguments[1].tagName) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            // workaround firefox 1.0.X bug
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1])
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName.toUpperCase() != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
          }
        }

    // text, or array of children
    if(arguments[2])
      this._children(element, arguments[2]);

     return $(element);
  },
  _text: function(text) {
     return document.createTextNode(text);
  },

  ATTR_MAP: {
    'className': 'class',
    'htmlFor': 'for'
  },

  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(children.tagName) {
      element.appendChild(children);
      return;
    }
    if(typeof children=='object') { // array can hold nodes and text
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e);
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children))
        element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  },
  build: function(html) {
    var element = this.node('div');
    $(element).update(html.strip());
    return element.down();
  },
  dump: function(scope) {
    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope

    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);

    tags.each( function(tag){
      scope[tag] = function() {
        return Builder.node.apply(Builder, [tag].concat($A(arguments)));
      };
    });
  }
};// script.aculo.us effects.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// converts rgb() and #xxx to #xxxxxx format,
// returns self (or first argument) if not convertable
String.prototype.parseColor = function() {
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {
    var cols = this.slice(4,this.length-1).split(',');
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  } else {
    if (this.slice(0,1) == '#') {
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
      if (this.length==7) color = this.toLowerCase();
    }
  }
  return (color.length==7 ? color : (arguments[0] || this));
};

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue :
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
};

Element.collectTextNodesIgnoreClass = function(element, className) {
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue :
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
};

Element.setContentZoom = function(element, percent) {
  element = $(element);
  element.setStyle({fontSize: (percent/100) + 'em'});
  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
};

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
};

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  Transitions: {
    linear: Prototype.K,
    sinoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + .5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
    },
    pulse: function(pos, pulses) {
      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
    },
    spring: function(pos) {
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
    },
    none: function(pos) {
      return 0;
    },
    full: function(pos) {
      return 1;
    }
  },
  DefaultOptions: {
    duration:   1.0,   // seconds
    fps:        100,   // 100= assume 66fps max.
    sync:       false, // true for combining
    from:       0.0,
    to:         1.0,
    delay:      0.0,
    queue:      'parallel'
  },
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';

    element = $(element);
    $A(element.childNodes).each( function(child) {
      if (child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            new Element('span', {style: tagifyStyle}).update(
              character == ' ' ? String.fromCharCode(160) : character),
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if (((typeof element == 'object') ||
        Object.isFunction(element)) &&
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;

    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || { });
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || { });
    Effect[element.visible() ?
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create(Enumerable, {
  initialize: function() {
    this.effects  = [];
    this.interval = null;
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();

    var position = Object.isString(effect.options.queue) ?
      effect.options.queue : effect.options.queue.position;

    switch(position) {
      case 'front':
        // move unstarted effects after this effect
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }

    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);

    if (!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if (this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++)
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if (!Object.isString(queueName)) return queueName;

    return this.instances.get(queueName) ||
      this.instances.set(queueName, new Effect.ScopedQueue());
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.Base = Class.create({
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;

    this.render = (function() {
      function dispatch(effect, eventName) {
        if (effect.options[eventName + 'Internal'])
          effect.options[eventName + 'Internal'](effect);
        if (effect.options[eventName])
          effect.options[eventName](effect);
      }

      return function(pos) {
        if (this.state === "idle") {
          this.state = "running";
          dispatch(this, 'beforeSetup');
          if (this.setup) this.setup();
          dispatch(this, 'afterSetup');
        }
        if (this.state === "running") {
          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
          this.position = pos;
          dispatch(this, 'beforeUpdate');
          if (this.update) this.update(pos);
          dispatch(this, 'afterUpdate');
        }
      };
    })();

    this.event('beforeStart');
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ?
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if (timePos >= this.startOn) {
      if (timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if (this.finish) this.finish();
        this.event('afterFinish');
        return;
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = (pos * this.totalFrames).round();
      if (frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ?
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if (!Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
});

Effect.Parallel = Class.create(Effect.Base, {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if (effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Tween = Class.create(Effect.Base, {
  initialize: function(object, from, to) {
    object = Object.isString(object) ? $(object) : object;
    var args = $A(arguments), method = args.last(),
      options = args.length == 5 ? args[3] : null;
    this.method = Object.isFunction(method) ? method.bind(object) :
      Object.isFunction(object[method]) ? object[method].bind(object) :
      function(value) { object[method] = value };
    this.start(Object.extend({ from: from, to: to }, options || { }));
  },
  update: function(position) {
    this.method(position);
  }
});

Effect.Event = Class.create(Effect.Base, {
  initialize: function() {
    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || { });
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if (this.options.mode == 'absolute') {
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: (this.options.x  * position + this.originalLeft).round() + 'px',
      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element,
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
};

Effect.Scale = Class.create(Effect.Base, {
  initialize: function(element, percent) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || { });
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');

    this.originalStyle = { };
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));

    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;

    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if (fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));

    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;

    this.dims = null;
    if (this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if (/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if (!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if (this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = { };
    if (this.options.scaleX) d.width = width.round() + 'px';
    if (this.options.scaleY) d.height = height.round() + 'px';
    if (this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if (this.elementPositioning == 'absolute') {
        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if (this.options.scaleY) d.top = -topd + 'px';
        if (this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = { };
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if (!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if (!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = function(element) {
  var options = arguments[1] || { },
  scrollOffsets = document.viewport.getScrollOffsets(),
  elementOffsets = $(element).cumulativeOffset();

  if (options.offset) elementOffsets[1] += options.offset;

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1],
    options,
    function(p){ scrollTo(scrollOffsets.left, p.round()); }
  );
};

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
    from: element.getOpacity() || 1.0,
    to:   0.0,
    afterFinishInternal: function(effect) {
      if (effect.options.to!=0) return;
      effect.element.hide().setStyle({opacity: oldOpacity});
    }
  }, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show();
  }}, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = {
    opacity: element.getInlineOpacity(),
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200,
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
     Object.extend({ duration: 1.0,
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element);
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || { })
   );
};

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false,
      scaleX: false,
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      }
    }, arguments[1] || { })
  );
};

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || { }));
};

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, {
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) {
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      });
    }
  }, arguments[1] || { }));
};

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned();
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        }
      }, arguments[1] || { }));
};

Effect.Shake = function(element) {
  element = $(element);
  var options = Object.extend({
    distance: 20,
    duration: 0.5
  }, arguments[1] || {});
  var distance = parseFloat(options.distance);
  var split = parseFloat(options.duration) / 10.0;
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element,
      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}); }}); }}); }}); }}); }});
};

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || { })
  );
};

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false,
    scaleX: false,
    scaleMode: 'box',
    scaleFrom: 100,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
    }
   }, arguments[1] || { })
  );
};

// Bug in opera makes the TD containing this element expand for a instance after finish
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, {
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping();
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping();
    }
  });
};

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var initialMoveX, initialMoveY;
  var moveX, moveY;

  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0;
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }

  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01,
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show();
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
             }
           }, options)
      );
    }
  });
};

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;

  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }

  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping();
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
};

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || { },
    oldOpacity = element.getInlineOpacity(),
    transition = options.transition || Effect.Transitions.linear,
    reverser   = function(pos){
      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
    };

  return new Effect.Opacity(element,
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
};

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, {
      scaleContent: false,
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || { }));
};

Effect.Morph = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: { }
    }, arguments[1] || { });

    if (!Object.isString(options.style)) this.style = $H(options.style);
    else {
      if (options.style.include(':'))
        this.style = options.style.parseStyle();
      else {
        this.element.addClassName(options.style);
        this.style = $H(this.element.getStyles());
        this.element.removeClassName(options.style);
        var css = this.element.getStyles();
        this.style = this.style.reject(function(style) {
          return style.value == css[style.key];
        });
        options.afterFinishInternal = function(effect) {
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            effect.element.style[transform.style] = '';
          });
        };
      }
    }
    this.start(options);
  },

  setup: function(){
    function parseColor(color){
      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 );
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if (property == 'opacity') {
        value = parseFloat(value);
        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if (Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return {
        style: property.camelize(),
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      );
    });
  },
  update: function(position) {
    var style = { }, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] =
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        (transform.originalValue +
          (transform.targetValue - transform.originalValue) * position).toFixed(3) +
            (transform.unit === null ? '' : transform.unit);
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create({
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || { };
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      track = $H(track);
      var data = track.values().first();
      this.tracks.push($H({
        ids:     track.keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
        var elements = [$(ids) || $$(ids)].flatten();
        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');

Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
  var style, styleRules = $H();
  if (Prototype.Browser.WebKit)
    style = new Element('div',{style:this}).style;
  else {
    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
    style = String.__parseStyleElement.childNodes[0].style;
  }

  Element.CSS_PROPERTIES.each(function(property){
    if (style[property]) styleRules.set(property, style[property]);
  });

  if (Prototype.Browser.IE && this.include('opacity'))
    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);

  return styleRules;
};

if (document.defaultView && document.defaultView.getComputedStyle) {
  Element.getStyles = function(element) {
    var css = document.defaultView.getComputedStyle($(element), null);
    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
      styles[property] = css[property];
      return styles;
    });
  };
} else {
  Element.getStyles = function(element) {
    element = $(element);
    var css = element.currentStyle, styles;
    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
      results[property] = css[property];
      return results;
    });
    if (!styles.opacity) styles.opacity = element.getOpacity();
    return styles;
  };
}

Effect.Methods = {
  morph: function(element, style) {
    element = $(element);
    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
    return element;
  },
  visualEffect: function(element, effect, options) {
    element = $(element);
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
    return element;
  },
  highlight: function(element, options) {
    element = $(element);
    new Effect.Highlight(element, options);
    return element;
  }
};

$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  'pulsate shake puff squish switchOff dropOut').each(
  function(effect) {
    Effect.Methods[effect] = function(element, options){
      element = $(element);
      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
      return element;
    };
  }
);

$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
  function(f) { Effect.Methods[f] = Element[f]; }
);

Element.addMethods(Effect.Methods);// script.aculo.us dragdrop.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

if(Object.isUndefined(Effect))
  throw("dragdrop.js requires including script.aculo.us' effects.js library");

var Droppables = {
  drops: [],

  remove: function(element) {
    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  },

  add: function(element) {
    element = $(element);
    var options = Object.extend({
      greedy:     true,
      hoverclass: null,
      tree:       false
    }, arguments[1] || { });

    // cache containers
    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if(Object.isArray(containment)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }

    if(options.accept) options.accept = [options.accept].flatten();

    Element.makePositioned(element); // fix IE
    options.element = element;

    this.drops.push(options);
  },

  findDeepestChild: function(drops) {
    deepest = drops[0];

    for (i = 1; i < drops.length; ++i)
      if (Element.isParent(drops[i].element, deepest.element))
        deepest = drops[i];

    return deepest;
  },

  isContained: function(element, drop) {
    var containmentNode;
    if(drop.tree) {
      containmentNode = element.treeNode;
    } else {
      containmentNode = element.parentNode;
    }
    return drop._containers.detect(function(c) { return containmentNode == c });
  },

  isAffected: function(point, element, drop) {
    return (
      (drop.element!=element) &&
      ((!drop._containers) ||
        this.isContained(element, drop)) &&
      ((!drop.accept) ||
        (Element.classNames(element).detect(
          function(v) { return drop.accept.include(v) } ) )) &&
      Position.within(drop.element, point[0], point[1]) );
  },

  deactivate: function(drop) {
    if(drop.hoverclass)
      Element.removeClassName(drop.element, drop.hoverclass);
    this.last_active = null;
  },

  activate: function(drop) {
    if(drop.hoverclass)
      Element.addClassName(drop.element, drop.hoverclass);
    this.last_active = drop;
  },

  show: function(point, element) {
    if(!this.drops.length) return;
    var drop, affected = [];

    this.drops.each( function(drop) {
      if(Droppables.isAffected(point, element, drop))
        affected.push(drop);
    });

    if(affected.length>0)
      drop = Droppables.findDeepestChild(affected);

    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
    if (drop) {
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));

      if (drop != this.last_active) Droppables.activate(drop);
    }
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
      if (this.last_active.onDrop) {
        this.last_active.onDrop(element, this.last_active.element, event);
        return true;
      }
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
};

var Draggables = {
  drags: [],
  observers: [],

  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);

      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },

  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },

  activate: function(draggable) {
    if(draggable.options.delay) {
      this._timeout = setTimeout(function() {
        Draggables._timeout = null;
        window.focus();
        Draggables.activeDraggable = draggable;
      }.bind(this), draggable.options.delay);
    } else {
      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
      this.activeDraggable = draggable;
    }
  },

  deactivate: function() {
    this.activeDraggable = null;
  },

  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    // Mozilla-based browsers fire successive mousemove events with
    // the same coordinates, prevent needless redrawing (moz bug?)
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;

    this.activeDraggable.updateDrag(event, pointer);
  },

  endDrag: function(event) {
    if(this._timeout) {
      clearTimeout(this._timeout);
      this._timeout = null;
    }
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },

  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },

  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },

  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },

  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  },

  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
};

/*--------------------------------------------------------------------------*/

var Draggable = Class.create({
  initialize: function(element) {
    var defaults = {
      handle: false,
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
          queue: {scope:'_draggable', position:'end'}
        });
      },
      endeffect: function(element) {
        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){
            Draggable._dragging[element] = false
          }
        });
      },
      zindex: 1000,
      revert: false,
      quiet: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
      delay: 0
    };

    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
      Object.extend(defaults, {
        starteffect: function(element) {
          element._opacity = Element.getOpacity(element);
          Draggable._dragging[element] = true;
          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
        }
      });

    var options = Object.extend(defaults, arguments[1] || { });

    this.element = $(element);

    if(options.handle && Object.isString(options.handle))
      this.handle = this.element.down('.'+options.handle, 0);

    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;

    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
      options.scroll = $(options.scroll);
      this._isScrollChild = Element.childOf(this.element, options.scroll);
    }

    Element.makePositioned(this.element); // fix IE

    this.options  = options;
    this.dragging = false;

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);

    Draggables.register(this);
  },

  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Draggables.unregister(this);
  },

  currentDelta: function() {
    return([
      parseInt(Element.getStyle(this.element,'left') || '0'),
      parseInt(Element.getStyle(this.element,'top') || '0')]);
  },

  initDrag: function(event) {
    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
      Draggable._dragging[this.element]) return;
    if(Event.isLeftClick(event)) {
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if((tag_name = src.tagName.toUpperCase()) && (
        tag_name=='INPUT' ||
        tag_name=='SELECT' ||
        tag_name=='OPTION' ||
        tag_name=='BUTTON' ||
        tag_name=='TEXTAREA')) return;

      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos     = Position.cumulativeOffset(this.element);
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });

      Draggables.activate(this);
      Event.stop(event);
    }
  },

  startDrag: function(event) {
    this.dragging = true;
    if(!this.delta)
      this.delta = this.currentDelta();

    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }

    if(this.options.ghosting) {
      this._clone = this.element.cloneNode(true);
      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
      if (!this._originallyAbsolute)
        Position.absolutize(this.element);
      this.element.parentNode.insertBefore(this._clone, this.element);
    }

    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }

    Draggables.notify('onStart', this, event);

    if(this.options.starteffect) this.options.starteffect(this.element);
  },

  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);

    if(!this.options.quiet){
      Position.prepare();
      Droppables.show(pointer, this.element);
    }

    Draggables.notify('onDrag', this, event);

    this.draw(pointer);
    if(this.options.change) this.options.change(this);

    if(this.options.scroll) {
      this.stopScrolling();

      var p;
      if (this.options.scroll == window) {
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
      } else {
        p = Position.page(this.options.scroll);
        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
        p[1] += this.options.scroll.scrollTop + Position.deltaY;
        p.push(p[0]+this.options.scroll.offsetWidth);
        p.push(p[1]+this.options.scroll.offsetHeight);
      }
      var speed = [0,0];
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
      this.startScrolling(speed);
    }

    // fix AppleWebKit rendering
    if(Prototype.Browser.WebKit) window.scrollBy(0,0);

    Event.stop(event);
  },

  finishDrag: function(event, success) {
    this.dragging = false;

    if(this.options.quiet){
      Position.prepare();
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      Droppables.show(pointer, this.element);
    }

    if(this.options.ghosting) {
      if (!this._originallyAbsolute)
        Position.relativize(this.element);
      delete this._originallyAbsolute;
      Element.remove(this._clone);
      this._clone = null;
    }

    var dropped = false;
    if(success) {
      dropped = Droppables.fire(event, this.element);
      if (!dropped) dropped = false;
    }
    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && Object.isFunction(revert)) revert = revert(this.element);

    var d = this.currentDelta();
    if(revert && this.options.reverteffect) {
      if (dropped == 0 || revert != 'failure')
        this.options.reverteffect(this.element,
          d[1]-this.delta[1], d[0]-this.delta[0]);
    } else {
      this.delta = d;
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect)
      this.options.endeffect(this.element);

    Draggables.deactivate(this);
    Droppables.reset();
  },

  keyPress: function(event) {
    if(event.keyCode!=Event.KEY_ESC) return;
    this.finishDrag(event, false);
    Event.stop(event);
  },

  endDrag: function(event) {
    if(!this.dragging) return;
    this.stopScrolling();
    this.finishDrag(event, true);
    Event.stop(event);
  },

  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    if(this.options.ghosting) {
      var r   = Position.realOffset(this.element);
      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
    }

    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];

    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }

    var p = [0,1].map(function(i){
      return (point[i]-pos[i]-this.offset[i])
    }.bind(this));

    if(this.options.snap) {
      if(Object.isFunction(this.options.snap)) {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(Object.isArray(this.options.snap)) {
        p = p.map( function(v, i) {
          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
      } else {
        p = p.map( function(v) {
          return (v/this.options.snap).round()*this.options.snap }.bind(this));
      }
    }}

    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";

    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },

  stopScrolling: function() {
    if(this.scrollInterval) {
      clearInterval(this.scrollInterval);
      this.scrollInterval = null;
      Draggables._lastScrollPointer = null;
    }
  },

  startScrolling: function(speed) {
    if(!(speed[0] || speed[1])) return;
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
    this.lastScrolled = new Date();
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  },

  scroll: function() {
    var current = new Date();
    var delta = current - this.lastScrolled;
    this.lastScrolled = current;
    if(this.options.scroll == window) {
      with (this._getWindowScroll(this.options.scroll)) {
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
          var d = delta / 1000;
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
        }
      }
    } else {
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
    }

    Position.prepare();
    Droppables.show(Draggables._lastPointer, this.element);
    Draggables.notify('onDrag', this);
    if (this._isScrollChild) {
      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
      if (Draggables._lastScrollPointer[0] < 0)
        Draggables._lastScrollPointer[0] = 0;
      if (Draggables._lastScrollPointer[1] < 0)
        Draggables._lastScrollPointer[1] = 0;
      this.draw(Draggables._lastScrollPointer);
    }

    if(this.options.change) this.options.change(this);
  },

  _getWindowScroll: function(w) {
    var T, L, W, H;
    with (w.document) {
      if (w.document.documentElement && documentElement.scrollTop) {
        T = documentElement.scrollTop;
        L = documentElement.scrollLeft;
      } else if (w.document.body) {
        T = body.scrollTop;
        L = body.scrollLeft;
      }
      if (w.innerWidth) {
        W = w.innerWidth;
        H = w.innerHeight;
      } else if (w.document.documentElement && documentElement.clientWidth) {
        W = documentElement.clientWidth;
        H = documentElement.clientHeight;
      } else {
        W = body.offsetWidth;
        H = body.offsetHeight;
      }
    }
    return { top: T, left: L, width: W, height: H };
  }
});

Draggable._dragging = { };

/*--------------------------------------------------------------------------*/

var SortableObserver = Class.create({
  initialize: function(element, observer) {
    this.element   = $(element);
    this.observer  = observer;
    this.lastValue = Sortable.serialize(this.element);
  },

  onStart: function() {
    this.lastValue = Sortable.serialize(this.element);
  },

  onEnd: function() {
    Sortable.unmark();
    if(this.lastValue != Sortable.serialize(this.element))
      this.observer(this.element)
  }
});

var Sortable = {
  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,

  sortables: { },

  _findRootElement: function(element) {
    while (element.tagName.toUpperCase() != "BODY") {
      if(element.id && Sortable.sortables[element.id]) return element;
      element = element.parentNode;
    }
  },

  options: function(element) {
    element = Sortable._findRootElement($(element));
    if(!element) return;
    return Sortable.sortables[element.id];
  },

  destroy: function(element){
    element = $(element);
    var s = Sortable.sortables[element.id];

    if(s) {
      Draggables.removeObserver(s.element);
      s.droppables.each(function(d){ Droppables.remove(d) });
      s.draggables.invoke('destroy');

      delete Sortable.sortables[s.element.id];
    }
  },

  create: function(element) {
    element = $(element);
    var options = Object.extend({
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      delay:       0,
      hoverclass:  null,
      ghosting:    false,
      quiet:       false,
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      this.SERIALIZE_RULE,

      // these take arrays of elements or ids and can be
      // used for better initialization performance
      elements:    false,
      handles:     false,

      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || { });

    // clear any old sortable with same element
    this.destroy(element);

    // build options for the draggables
    var options_for_draggable = {
      revert:      true,
      quiet:       options.quiet,
      scroll:      options.scroll,
      scrollSpeed: options.scrollSpeed,
      scrollSensitivity: options.scrollSensitivity,
      delay:       options.delay,
      ghosting:    options.ghosting,
      constraint:  options.constraint,
      handle:      options.handle };

    if(options.starteffect)
      options_for_draggable.starteffect = options.starteffect;

    if(options.reverteffect)
      options_for_draggable.reverteffect = options.reverteffect;
    else
      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
        element.style.top  = 0;
        element.style.left = 0;
      };

    if(options.endeffect)
      options_for_draggable.endeffect = options.endeffect;

    if(options.zindex)
      options_for_draggable.zindex = options.zindex;

    // build options for the droppables
    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      tree:        options.tree,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover
    };

    var options_for_tree = {
      onHover:      Sortable.onEmptyHover,
      overlap:      options.overlap,
      containment:  options.containment,
      hoverclass:   options.hoverclass
    };

    // fix for gecko engine
    Element.cleanWhitespace(element);

    options.draggables = [];
    options.droppables = [];

    // drop on empty handling
    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
      var handle = options.handles ? $(options.handles[i]) :
        (options.handle ? $(e).select('.' + options.handle)[0] : e);
      options.draggables.push(
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
      Droppables.add(e, options_for_droppable);
      if(options.tree) e.treeNode = element;
      options.droppables.push(e);
    });

    if(options.tree) {
      (Sortable.findTreeElements(element, options) || []).each( function(e) {
        Droppables.add(e, options_for_tree);
        e.treeNode = element;
        options.droppables.push(e);
      });
    }

    // keep reference
    this.sortables[element.id] = options;

    // for onupdate
    Draggables.addObserver(new SortableObserver(element, options.onUpdate));

  },

  // return all suitable-for-sortable elements in a guaranteed order
  findElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.tag);
  },

  findTreeElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.treeTag);
  },

  onHover: function(element, dropon, overlap) {
    if(Element.isParent(dropon, element)) return;

    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
      return;
    } else if(overlap>0.5) {
      Sortable.mark(dropon, 'before');
      if(dropon.previousSibling != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, dropon);
        if(dropon.parentNode!=oldParentNode)
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    } else {
      Sortable.mark(dropon, 'after');
      var nextElement = dropon.nextSibling || null;
      if(nextElement != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, nextElement);
        if(dropon.parentNode!=oldParentNode)
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    }
  },

  onEmptyHover: function(element, dropon, overlap) {
    var oldParentNode = element.parentNode;
    var droponOptions = Sortable.options(dropon);

    if(!Element.isParent(dropon, element)) {
      var index;

      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
      var child = null;

      if(children) {
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);

        for (index = 0; index < children.length; index += 1) {
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
            child = index + 1 < children.length ? children[index + 1] : null;
            break;
          } else {
            child = children[index];
            break;
          }
        }
      }

      dropon.insertBefore(element, child);

      Sortable.options(oldParentNode).onChange(element);
      droponOptions.onChange(element);
    }
  },

  unmark: function() {
    if(Sortable._marker) Sortable._marker.hide();
  },

  mark: function(dropon, position) {
    // mark on ghosting only
    var sortable = Sortable.options(dropon.parentNode);
    if(sortable && !sortable.ghosting) return;

    if(!Sortable._marker) {
      Sortable._marker =
        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
          hide().addClassName('dropmarker').setStyle({position:'absolute'});
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
    }
    var offsets = Position.cumulativeOffset(dropon);
    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});

    if(position=='after')
      if(sortable.overlap == 'horizontal')
        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
      else
        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});

    Sortable._marker.show();
  },

  _tree: function(element, options, parent) {
    var children = Sortable.findElements(element, options) || [];

    for (var i = 0; i < children.length; ++i) {
      var match = children[i].id.match(options.format);

      if (!match) continue;

      var child = {
        id: encodeURIComponent(match ? match[1] : null),
        element: element,
        parent: parent,
        children: [],
        position: parent.children.length,
        container: $(children[i]).down(options.treeTag)
      };

      /* Get the element containing the children and recurse over it */
      if (child.container)
        this._tree(child.container, options, child);

      parent.children.push (child);
    }

    return parent;
  },

  tree: function(element) {
    element = $(element);
    var sortableOptions = this.options(element);
    var options = Object.extend({
      tag: sortableOptions.tag,
      treeTag: sortableOptions.treeTag,
      only: sortableOptions.only,
      name: element.id,
      format: sortableOptions.format
    }, arguments[1] || { });

    var root = {
      id: null,
      parent: null,
      children: [],
      container: element,
      position: 0
    };

    return Sortable._tree(element, options, root);
  },

  /* Construct a [i] index for a particular node */
  _constructIndex: function(node) {
    var index = '';
    do {
      if (node.id) index = '[' + node.position + ']' + index;
    } while ((node = node.parent) != null);
    return index;
  },

  sequence: function(element) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[1] || { });

    return $(this.findElements(element, options) || []).map( function(item) {
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
    });
  },

  setSequence: function(element, new_sequence) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[2] || { });

    var nodeMap = { };
    this.findElements(element, options).each( function(n) {
        if (n.id.match(options.format))
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
        n.parentNode.removeChild(n);
    });

    new_sequence.each(function(ident) {
      var n = nodeMap[ident];
      if (n) {
        n[1].appendChild(n[0]);
        delete nodeMap[ident];
      }
    });
  },

  serialize: function(element) {
    element = $(element);
    var options = Object.extend(Sortable.options(element), arguments[1] || { });
    var name = encodeURIComponent(
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);

    if (options.tree) {
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
        return [name + Sortable._constructIndex(item) + "[id]=" +
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
      }).flatten().join('&');
    } else {
      return Sortable.sequence(element, arguments[1]).map( function(item) {
        return name + "[]=" + encodeURIComponent(item);
      }).join('&');
    }
  }
};

// Returns true if child is contained within element
Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;
  if (child.parentNode == element) return true;
  return Element.isParent(child.parentNode, element);
};

Element.findChildren = function(element, only, recursive, tagName) {
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
};

Element.offsetSize = function (element, type) {
  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
};// script.aculo.us controls.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.

if(typeof Effect == 'undefined')
  throw("controls.js requires including script.aculo.us' effects.js library");

var Autocompleter = { };
Autocompleter.Base = Class.create({
  baseInitialize: function(element, update, options) {
    element          = $(element);
    this.element     = element;
    this.update      = $(update);
    this.hasFocus    = false;
    this.changed     = false;
    this.active      = false;
    this.index       = 0;
    this.entryCount  = 0;
    this.oldElementValue = this.element.value;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || { };

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow ||
      function(element, update){
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false,
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide ||
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string')
      this.options.tokens = new Array(this.options.tokens);
    // Force carriage returns as token delimiters anyway
    if (!this.options.tokens.include('\n'))
      this.options.tokens.push('\n');

    this.observer = null;

    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix &&
      (Prototype.Browser.IE) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update,
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },

  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator) Element.show(this.options.indicator);
  },

  stopIndicator: function() {
    if(this.options.indicator) Element.hide(this.options.indicator);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         Event.stop(event);
         return;
      }
     else
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer =
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex)
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },

  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },

  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;
  },

  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ?
          Element.addClassName(this.getEntry(i),"selected") :
          Element.removeClassName(this.getEntry(i),"selected");
      if(this.hasFocus) {
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },

  markPrevious: function() {
    if(this.index > 0) this.index--;
      else this.index = this.entryCount-1;
    this.getEntry(this.index).scrollIntoView(true);
  },

  markNext: function() {
    if(this.index < this.entryCount-1) this.index++;
      else this.index = 0;
    this.getEntry(this.index).scrollIntoView(false);
  },

  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },

  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },

  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = $(selectedElement).select('.' + this.options.select) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');

    var bounds = this.getTokenBounds();
    if (bounds[0] != -1) {
      var newValue = this.element.value.substr(0, bounds[0]);
      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
    } else {
      this.element.value = value;
    }
    this.oldElementValue = this.element.value;
    this.element.focus();

    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.down());

      if(this.update.firstChild && this.update.down().childNodes) {
        this.entryCount =
          this.update.down().childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else {
        this.entryCount = 0;
      }

      this.stopIndicator();
      this.index = 0;

      if(this.entryCount==1 && this.options.autoSelect) {
        this.selectEntry();
        this.hide();
      } else {
        this.render();
      }
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;
    this.tokenBounds = null;
    if(this.getToken().length>=this.options.minChars) {
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
    this.oldElementValue = this.element.value;
  },

  getToken: function() {
    var bounds = this.getTokenBounds();
    return this.element.value.substring(bounds[0], bounds[1]).strip();
  },

  getTokenBounds: function() {
    if (null != this.tokenBounds) return this.tokenBounds;
    var value = this.element.value;
    if (value.strip().empty()) return [-1, 0];
    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
    var offset = (diff == this.oldElementValue.length ? 1 : 0);
    var prevTokenPos = -1, nextTokenPos = value.length;
    var tp;
    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
      if (tp > prevTokenPos) prevTokenPos = tp;
      tp = value.indexOf(this.options.tokens[index], diff + offset);
      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
    }
    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
  }
});

Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
  var boundary = Math.min(newS.length, oldS.length);
  for (var index = 0; index < boundary; ++index)
    if (newS[index] != oldS[index])
      return index;
  return boundary;
};

Ajax.Autocompleter = Class.create(Autocompleter.Base, {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    this.startIndicator();

    var entry = encodeURIComponent(this.options.paramName) + '=' +
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams)
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }
});

// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
//                    text only at the beginning of strings in the
//                    autocomplete array. Defaults to true, which will
//                    match text at the beginning of any *word* in the
//                    strings in the autocomplete array. If you want to
//                    search anywhere in the string, additionally set
//                    the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
//                   a partial match (unlike minChars, which defines
//                   how many characters are required to do any match
//                   at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
//                 Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.

Autocompleter.Local = Class.create(Autocompleter.Base, {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        var count     = 0;

        for (var i = 0; i < instance.options.array.length &&
          ret.length < instance.options.choices ; i++) {

          var elem = instance.options.array[i];
          var foundPos = instance.options.ignoreCase ?
            elem.toLowerCase().indexOf(entry.toLowerCase()) :
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) {
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
                elem.substr(entry.length) + "</li>");
              break;
            } else if (entry.length >= instance.options.partialChars &&
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                  foundPos + entry.length) + "</li>");
                break;
              }
            }

            foundPos = instance.options.ignoreCase ?
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
              elem.indexOf(entry, foundPos + 1);

          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
        return "<ul>" + ret.join('') + "</ul>";
      }
    }, options || { });
  }
});

// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).

// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
};

Ajax.InPlaceEditor = Class.create({
  initialize: function(element, url, options) {
    this.url = url;
    this.element = element = $(element);
    this.prepareOptions();
    this._controls = { };
    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
    Object.extend(this.options, options || { });
    if (!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + '-inplaceeditor';
      if ($(this.options.formId))
        this.options.formId = '';
    }
    if (this.options.externalControl)
      this.options.externalControl = $(this.options.externalControl);
    if (!this.options.externalControl)
      this.options.externalControlOnly = false;
    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
    this.element.title = this.options.clickToEditText;
    this._boundCancelHandler = this.handleFormCancellation.bind(this);
    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
    this._boundWrapperHandler = this.wrapUp.bind(this);
    this.registerListeners();
  },
  checkForEscapeOrReturn: function(e) {
    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
    if (Event.KEY_ESC == e.keyCode)
      this.handleFormCancellation(e);
    else if (Event.KEY_RETURN == e.keyCode)
      this.handleFormSubmission(e);
  },
  createControl: function(mode, handler, extraClasses) {
    var control = this.options[mode + 'Control'];
    var text = this.options[mode + 'Text'];
    if ('button' == control) {
      var btn = document.createElement('input');
      btn.type = 'submit';
      btn.value = text;
      btn.className = 'editor_' + mode + '_button';
      if ('cancel' == mode)
        btn.onclick = this._boundCancelHandler;
      this._form.appendChild(btn);
      this._controls[mode] = btn;
    } else if ('link' == control) {
      var link = document.createElement('a');
      link.href = '#';
      link.appendChild(document.createTextNode(text));
      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
      link.className = 'editor_' + mode + '_link';
      if (extraClasses)
        link.className += ' ' + extraClasses;
      this._form.appendChild(link);
      this._controls[mode] = link;
    }
  },
  createEditField: function() {
    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
    var fld;
    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
      fld = document.createElement('input');
      fld.type = 'text';
      var size = this.options.size || this.options.cols || 0;
      if (0 < size) fld.size = size;
    } else {
      fld = document.createElement('textarea');
      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
      fld.cols = this.options.cols || 40;
    }
    fld.name = this.options.paramName;
    fld.value = text; // No HTML breaks conversion anymore
    fld.className = 'editor_field';
    if (this.options.submitOnBlur)
      fld.onblur = this._boundSubmitHandler;
    this._controls.editor = fld;
    if (this.options.loadTextURL)
      this.loadExternalText();
    this._form.appendChild(this._controls.editor);
  },
  createForm: function() {
    var ipe = this;
    function addText(mode, condition) {
      var text = ipe.options['text' + mode + 'Controls'];
      if (!text || condition === false) return;
      ipe._form.appendChild(document.createTextNode(text));
    };
    this._form = $(document.createElement('form'));
    this._form.id = this.options.formId;
    this._form.addClassName(this.options.formClassName);
    this._form.onsubmit = this._boundSubmitHandler;
    this.createEditField();
    if ('textarea' == this._controls.editor.tagName.toLowerCase())
      this._form.appendChild(document.createElement('br'));
    if (this.options.onFormCustomization)
      this.options.onFormCustomization(this, this._form);
    addText('Before', this.options.okControl || this.options.cancelControl);
    this.createControl('ok', this._boundSubmitHandler);
    addText('Between', this.options.okControl && this.options.cancelControl);
    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
    addText('After', this.options.okControl || this.options.cancelControl);
  },
  destroy: function() {
    if (this._oldInnerHTML)
      this.element.innerHTML = this._oldInnerHTML;
    this.leaveEditMode();
    this.unregisterListeners();
  },
  enterEditMode: function(e) {
    if (this._saving || this._editing) return;
    this._editing = true;
    this.triggerCallback('onEnterEditMode');
    if (this.options.externalControl)
      this.options.externalControl.hide();
    this.element.hide();
    this.createForm();
    this.element.parentNode.insertBefore(this._form, this.element);
    if (!this.options.loadTextURL)
      this.postProcessEditField();
    if (e) Event.stop(e);
  },
  enterHover: function(e) {
    if (this.options.hoverClassName)
      this.element.addClassName(this.options.hoverClassName);
    if (this._saving) return;
    this.triggerCallback('onEnterHover');
  },
  getText: function() {
    return this.element.innerHTML.unescapeHTML();
  },
  handleAJAXFailure: function(transport) {
    this.triggerCallback('onFailure', transport);
    if (this._oldInnerHTML) {
      this.element.innerHTML = this._oldInnerHTML;
      this._oldInnerHTML = null;
    }
  },
  handleFormCancellation: function(e) {
    this.wrapUp();
    if (e) Event.stop(e);
  },
  handleFormSubmission: function(e) {
    var form = this._form;
    var value = $F(this._controls.editor);
    this.prepareSubmission();
    var params = this.options.callback(form, value) || '';
    if (Object.isString(params))
      params = params.toQueryParams();
    params.editorId = this.element.id;
    if (this.options.htmlResponse) {
      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
      Object.extend(options, {
        parameters: params,
        onComplete: this._boundWrapperHandler,
        onFailure: this._boundFailureHandler
      });
      new Ajax.Updater({ success: this.element }, this.url, options);
    } else {
      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
      Object.extend(options, {
        parameters: params,
        onComplete: this._boundWrapperHandler,
        onFailure: this._boundFailureHandler
      });
      new Ajax.Request(this.url, options);
    }
    if (e) Event.stop(e);
  },
  leaveEditMode: function() {
    this.element.removeClassName(this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this._originalBackground;
    this.element.show();
    if (this.options.externalControl)
      this.options.externalControl.show();
    this._saving = false;
    this._editing = false;
    this._oldInnerHTML = null;
    this.triggerCallback('onLeaveEditMode');
  },
  leaveHover: function(e) {
    if (this.options.hoverClassName)
      this.element.removeClassName(this.options.hoverClassName);
    if (this._saving) return;
    this.triggerCallback('onLeaveHover');
  },
  loadExternalText: function() {
    this._form.addClassName(this.options.loadingClassName);
    this._controls.editor.disabled = true;
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        this._form.removeClassName(this.options.loadingClassName);
        var text = transport.responseText;
        if (this.options.stripLoadedTextTags)
          text = text.stripTags();
        this._controls.editor.value = text;
        this._controls.editor.disabled = false;
        this.postProcessEditField();
      }.bind(this),
      onFailure: this._boundFailureHandler
    });
    new Ajax.Request(this.options.loadTextURL, options);
  },
  postProcessEditField: function() {
    var fpc = this.options.fieldPostCreation;
    if (fpc)
      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
  },
  prepareOptions: function() {
    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
      Object.extend(this.options, defs);
    }.bind(this));
  },
  prepareSubmission: function() {
    this._saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  registerListeners: function() {
    this._listeners = { };
    var listener;
    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
      listener = this[pair.value].bind(this);
      this._listeners[pair.key] = listener;
      if (!this.options.externalControlOnly)
        this.element.observe(pair.key, listener);
      if (this.options.externalControl)
        this.options.externalControl.observe(pair.key, listener);
    }.bind(this));
  },
  removeForm: function() {
    if (!this._form) return;
    this._form.remove();
    this._form = null;
    this._controls = { };
  },
  showSaving: function() {
    this._oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    this.element.addClassName(this.options.savingClassName);
    this.element.style.backgroundColor = this._originalBackground;
    this.element.show();
  },
  triggerCallback: function(cbName, arg) {
    if ('function' == typeof this.options[cbName]) {
      this.options[cbName](this, arg);
    }
  },
  unregisterListeners: function() {
    $H(this._listeners).each(function(pair) {
      if (!this.options.externalControlOnly)
        this.element.stopObserving(pair.key, pair.value);
      if (this.options.externalControl)
        this.options.externalControl.stopObserving(pair.key, pair.value);
    }.bind(this));
  },
  wrapUp: function(transport) {
    this.leaveEditMode();
    // Can't use triggerCallback due to backward compatibility: requires
    // binding + direct element
    this._boundComplete(transport, this.element);
  }
});

Object.extend(Ajax.InPlaceEditor.prototype, {
  dispose: Ajax.InPlaceEditor.prototype.destroy
});

Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
  initialize: function($super, element, url, options) {
    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
    $super(element, url, options);
  },

  createEditField: function() {
    var list = document.createElement('select');
    list.name = this.options.paramName;
    list.size = 1;
    this._controls.editor = list;
    this._collection = this.options.collection || [];
    if (this.options.loadCollectionURL)
      this.loadCollection();
    else
      this.checkForExternalText();
    this._form.appendChild(this._controls.editor);
  },

  loadCollection: function() {
    this._form.addClassName(this.options.loadingClassName);
    this.showLoadingText(this.options.loadingCollectionText);
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        var js = transport.responseText.strip();
        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
          throw('Server returned an invalid collection representation.');
        this._collection = eval(js);
        this.checkForExternalText();
      }.bind(this),
      onFailure: this.onFailure
    });
    new Ajax.Request(this.options.loadCollectionURL, options);
  },

  showLoadingText: function(text) {
    this._controls.editor.disabled = true;
    var tempOption = this._controls.editor.firstChild;
    if (!tempOption) {
      tempOption = document.createElement('option');
      tempOption.value = '';
      this._controls.editor.appendChild(tempOption);
      tempOption.selected = true;
    }
    tempOption.update((text || '').stripScripts().stripTags());
  },

  checkForExternalText: function() {
    this._text = this.getText();
    if (this.options.loadTextURL)
      this.loadExternalText();
    else
      this.buildOptionList();
  },

  loadExternalText: function() {
    this.showLoadingText(this.options.loadingText);
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        this._text = transport.responseText.strip();
        this.buildOptionList();
      }.bind(this),
      onFailure: this.onFailure
    });
    new Ajax.Request(this.options.loadTextURL, options);
  },

  buildOptionList: function() {
    this._form.removeClassName(this.options.loadingClassName);
    this._collection = this._collection.map(function(entry) {
      return 2 === entry.length ? entry : [entry, entry].flatten();
    });
    var marker = ('value' in this.options) ? this.options.value : this._text;
    var textFound = this._collection.any(function(entry) {
      return entry[0] == marker;
    }.bind(this));
    this._controls.editor.update('');
    var option;
    this._collection.each(function(entry, index) {
      option = document.createElement('option');
      option.value = entry[0];
      option.selected = textFound ? entry[0] == marker : 0 == index;
      option.appendChild(document.createTextNode(entry[1]));
      this._controls.editor.appendChild(option);
    }.bind(this));
    this._controls.editor.disabled = false;
    Field.scrollFreeActivate(this._controls.editor);
  }
});

//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only  exists for a while,  in order to  let ****
//**** users adapt to  the new API.  Read up on the new ****
//**** API and convert your code to it ASAP!            ****

Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
  if (!options) return;
  function fallback(name, expr) {
    if (name in options || expr === undefined) return;
    options[name] = expr;
  };
  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
    options.cancelLink == options.cancelButton == false ? false : undefined)));
  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
    options.okLink == options.okButton == false ? false : undefined)));
  fallback('highlightColor', options.highlightcolor);
  fallback('highlightEndColor', options.highlightendcolor);
};

Object.extend(Ajax.InPlaceEditor, {
  DefaultOptions: {
    ajaxOptions: { },
    autoRows: 3,                                // Use when multi-line w/ rows == 1
    cancelControl: 'link',                      // 'link'|'button'|false
    cancelText: 'cancel',
    clickToEditText: 'Click to edit',
    externalControl: null,                      // id|elt
    externalControlOnly: false,
    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
    formClassName: 'inplaceeditor-form',
    formId: null,                               // id|elt
    highlightColor: '#ffff99',
    highlightEndColor: '#ffffff',
    hoverClassName: '',
    htmlResponse: true,
    loadingClassName: 'inplaceeditor-loading',
    loadingText: 'Loading...',
    okControl: 'button',                        // 'link'|'button'|false
    okText: 'ok',
    paramName: 'value',
    rows: 1,                                    // If 1 and multi-line, uses autoRows
    savingClassName: 'inplaceeditor-saving',
    savingText: 'Saving...',
    size: 0,
    stripLoadedTextTags: false,
    submitOnBlur: false,
    textAfterControls: '',
    textBeforeControls: '',
    textBetweenControls: ''
  },
  DefaultCallbacks: {
    callback: function(form) {
      return Form.serialize(form);
    },
    onComplete: function(transport, element) {
      // For backward compatibility, this one is bound to the IPE, and passes
      // the element directly.  It was too often customized, so we don't break it.
      new Effect.Highlight(element, {
        startcolor: this.options.highlightColor, keepBackgroundImage: true });
    },
    onEnterEditMode: null,
    onEnterHover: function(ipe) {
      ipe.element.style.backgroundColor = ipe.options.highlightColor;
      if (ipe._effect)
        ipe._effect.cancel();
    },
    onFailure: function(transport, ipe) {
      alert('Error communication with the server: ' + transport.responseText.stripTags());
    },
    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
    onLeaveEditMode: null,
    onLeaveHover: function(ipe) {
      ipe._effect = new Effect.Highlight(ipe.element, {
        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
        restorecolor: ipe._originalBackground, keepBackgroundImage: true
      });
    }
  },
  Listeners: {
    click: 'enterEditMode',
    keydown: 'checkForEscapeOrReturn',
    mouseover: 'enterHover',
    mouseout: 'leaveHover'
  }
});

Ajax.InPlaceCollectionEditor.DefaultOptions = {
  loadingCollectionText: 'Loading options...'
};

// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create({
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element);
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
});// script.aculo.us slider.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Marty Haught, Thomas Fuchs
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

if (!Control) var Control = { };

// options:
//  axis: 'vertical', or 'horizontal' (default)
//
// callbacks:
//  onChange(value)
//  onSlide(value)
Control.Slider = Class.create({
  initialize: function(handle, track, options) {
    var slider = this;

    if (Object.isArray(handle)) {
      this.handles = handle.collect( function(e) { return $(e) });
    } else {
      this.handles = [$(handle)];
    }

    this.track   = $(track);
    this.options = options || { };

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step || '1');
    this.range     = this.options.range || $R(0,1);

    this.value     = 0; // assure backwards compat
    this.values    = this.handles.map( function() { return 0 });
    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;

    // Will be used to align the handle onto the track, if necessary
    this.alignX = parseInt(this.options.alignX || '0');
    this.alignY = parseInt(this.options.alignY || '0');

    this.trackLength = this.maximumOffset() - this.minimumOffset();

    this.handleLength = this.isVertical() ?
      (this.handles[0].offsetHeight != 0 ?
        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
        this.handles[0].style.width.replace(/px$/,""));

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    if (this.options.disabled) this.setDisabled();

    // Allowed values array
    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
    if (this.allowedValues) {
      this.minimum = this.allowedValues.min();
      this.maximum = this.allowedValues.max();
    }

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);

    // Initialize handles in reverse (make sure first handle is active)
    this.handles.each( function(h,i) {
      i = slider.handles.length-1-i;
      slider.setValue(parseFloat(
        (Object.isArray(slider.options.sliderValue) ?
          slider.options.sliderValue[i] : slider.options.sliderValue) ||
         slider.range.start), i);
      h.makePositioned().observe("mousedown", slider.eventMouseDown);
    });

    this.track.observe("mousedown", this.eventMouseDown);
    document.observe("mouseup", this.eventMouseUp);
    document.observe("mousemove", this.eventMouseMove);

    this.initialized = true;
  },
  dispose: function() {
    var slider = this;
    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    this.handles.each( function(h) {
      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
    });
  },
  setDisabled: function(){
    this.disabled = true;
  },
  setEnabled: function(){
    this.disabled = false;
  },
  getNearestValue: function(value){
    if (this.allowedValues){
      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
      if (value <= this.allowedValues.min()) return(this.allowedValues.min());

      var offset = Math.abs(this.allowedValues[0] - value);
      var newValue = this.allowedValues[0];
      this.allowedValues.each( function(v) {
        var currentOffset = Math.abs(v - value);
        if (currentOffset <= offset){
          newValue = v;
          offset = currentOffset;
        }
      });
      return newValue;
    }
    if (value > this.range.end) return this.range.end;
    if (value < this.range.start) return this.range.start;
    return value;
  },
  setValue: function(sliderValue, handleIdx){
    if (!this.active) {
      this.activeHandleIdx = handleIdx || 0;
      this.activeHandle    = this.handles[this.activeHandleIdx];
      this.updateStyles();
    }
    handleIdx = handleIdx || this.activeHandleIdx || 0;
    if (this.initialized && this.restricted) {
      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
        sliderValue = this.values[handleIdx-1];
      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
        sliderValue = this.values[handleIdx+1];
    }
    sliderValue = this.getNearestValue(sliderValue);
    this.values[handleIdx] = sliderValue;
    this.value = this.values[0]; // assure backwards compat

    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
      this.translateToPx(sliderValue);

    this.drawSpans();
    if (!this.dragging || !this.event) this.updateFinished();
  },
  setValueBy: function(delta, handleIdx) {
    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
      handleIdx || this.activeHandleIdx || 0);
  },
  translateToPx: function(value) {
    return Math.round(
      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
      (value - this.range.start)) + "px";
  },
  translateToValue: function(offset) {
    return ((offset/(this.trackLength-this.handleLength) *
      (this.range.end-this.range.start)) + this.range.start);
  },
  getRange: function(range) {
    var v = this.values.sortBy(Prototype.K);
    range = range || 0;
    return $R(v[range],v[range+1]);
  },
  minimumOffset: function(){
    return(this.isVertical() ? this.alignY : this.alignX);
  },
  maximumOffset: function(){
    return(this.isVertical() ?
      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
        this.track.style.height.replace(/px$/,"")) - this.alignY :
      (this.track.offsetWidth != 0 ? this.track.offsetWidth :
        this.track.style.width.replace(/px$/,"")) - this.alignX);
  },
  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  drawSpans: function() {
    var slider = this;
    if (this.spans)
      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
    if (this.options.startSpan)
      this.setSpan(this.options.startSpan,
        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
    if (this.options.endSpan)
      this.setSpan(this.options.endSpan,
        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  },
  setSpan: function(span, range) {
    if (this.isVertical()) {
      span.style.top = this.translateToPx(range.start);
      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
    } else {
      span.style.left = this.translateToPx(range.start);
      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
    }
  },
  updateStyles: function() {
    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
    Element.addClassName(this.activeHandle, 'selected');
  },
  startDrag: function(event) {
    if (Event.isLeftClick(event)) {
      if (!this.disabled){
        this.active = true;

        var handle = Event.element(event);
        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
        var track = handle;
        if (track==this.track) {
          var offsets  = Position.cumulativeOffset(this.track);
          this.event = event;
          this.setValue(this.translateToValue(
           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
          ));
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        } else {
          // find the handle (prevents issues with Safari)
          while((this.handles.indexOf(handle) == -1) && handle.parentNode)
            handle = handle.parentNode;

          if (this.handles.indexOf(handle)!=-1) {
            this.activeHandle    = handle;
            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
            this.updateStyles();

            var offsets  = Position.cumulativeOffset(this.activeHandle);
            this.offsetX = (pointer[0] - offsets[0]);
            this.offsetY = (pointer[1] - offsets[1]);
          }
        }
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if (this.active) {
      if (!this.dragging) this.dragging = true;
      this.draw(event);
      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
      Event.stop(event);
   }
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.track);
    pointer[0] -= this.offsetX + offsets[0];
    pointer[1] -= this.offsetY + offsets[1];
    this.event = event;
    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
    if (this.initialized && this.options.onSlide)
      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  },
  endDrag: function(event) {
    if (this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },
  finishDrag: function(event, success) {
    this.active = false;
    this.dragging = false;
    this.updateFinished();
  },
  updateFinished: function() {
    if (this.initialized && this.options.onChange)
      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
    this.event = null;
  }
});// script.aculo.us sound.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Based on code created by Jules Gravinese (http://www.webveteran.com/)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

Sound = {
  tracks: {},
  _enabled: true,
  template:
    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
  enable: function(){
    Sound._enabled = true;
  },
  disable: function(){
    Sound._enabled = false;
  },
  play: function(url){
    if(!Sound._enabled) return;
    var options = Object.extend({
      track: 'global', url: url, replace: false
    }, arguments[1] || {});

    if(options.replace && this.tracks[options.track]) {
      $R(0, this.tracks[options.track].id).each(function(id){
        var sound = $('sound_'+options.track+'_'+id);
        sound.Stop && sound.Stop();
        sound.remove();
      });
      this.tracks[options.track] = null;
    }

    if(!this.tracks[options.track])
      this.tracks[options.track] = { id: 0 };
    else
      this.tracks[options.track].id++;

    options.id = this.tracks[options.track].id;
    $$('body')[0].insert(
      Prototype.Browser.IE ? new Element('bgsound',{
        id: 'sound_'+options.track+'_'+options.id,
        src: options.url, loop: 1, autostart: true
      }) : Sound.template.evaluate(options));
  }
};

if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>');
  else
    Sound.play = function(){};
}/* SWFObject v2.1 <http://code.google.com/p/swfobject/>
	Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis
	This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
var swfobject=function(){var b="undefined",Q="object",n="Shockwave Flash",p="ShockwaveFlash.ShockwaveFlash",P="application/x-shockwave-flash",m="SWFObjectExprInst",j=window,K=document,T=navigator,o=[],N=[],i=[],d=[],J,Z=null,M=null,l=null,e=false,A=false;var h=function(){var v=typeof K.getElementById!=b&&typeof K.getElementsByTagName!=b&&typeof K.createElement!=b,AC=[0,0,0],x=null;if(typeof T.plugins!=b&&typeof T.plugins[n]==Q){x=T.plugins[n].description;if(x&&!(typeof T.mimeTypes!=b&&T.mimeTypes[P]&&!T.mimeTypes[P].enabledPlugin)){x=x.replace(/^.*\s+(\S+\s+\S+$)/,"$1");AC[0]=parseInt(x.replace(/^(.*)\..*$/,"$1"),10);AC[1]=parseInt(x.replace(/^.*\.(.*)\s.*$/,"$1"),10);AC[2]=/r/.test(x)?parseInt(x.replace(/^.*r(.*)$/,"$1"),10):0}}else{if(typeof j.ActiveXObject!=b){var y=null,AB=false;try{y=new ActiveXObject(p+".7")}catch(t){try{y=new ActiveXObject(p+".6");AC=[6,0,21];y.AllowScriptAccess="always"}catch(t){if(AC[0]==6){AB=true}}if(!AB){try{y=new ActiveXObject(p)}catch(t){}}}if(!AB&&y){try{x=y.GetVariable("$version");if(x){x=x.split(" ")[1].split(",");AC=[parseInt(x[0],10),parseInt(x[1],10),parseInt(x[2],10)]}}catch(t){}}}}var AD=T.userAgent.toLowerCase(),r=T.platform.toLowerCase(),AA=/webkit/.test(AD)?parseFloat(AD.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,q=false,z=r?/win/.test(r):/win/.test(AD),w=r?/mac/.test(r):/mac/.test(AD);/*@cc_on q=true;@if(@_win32)z=true;@elif(@_mac)w=true;@end@*/return{w3cdom:v,pv:AC,webkit:AA,ie:q,win:z,mac:w}}();var L=function(){if(!h.w3cdom){return }f(H);if(h.ie&&h.win){try{K.write("<script id=__ie_ondomload defer=true src=//:><\/script>");J=C("__ie_ondomload");if(J){I(J,"onreadystatechange",S)}}catch(q){}}if(h.webkit&&typeof K.readyState!=b){Z=setInterval(function(){if(/loaded|complete/.test(K.readyState)){E()}},10)}if(typeof K.addEventListener!=b){K.addEventListener("DOMContentLoaded",E,null)}R(E)}();function S(){if(J.readyState=="complete"){J.parentNode.removeChild(J);E()}}function E(){if(e){return }if(h.ie&&h.win){var v=a("span");try{var u=K.getElementsByTagName("body")[0].appendChild(v);u.parentNode.removeChild(u)}catch(w){return }}e=true;if(Z){clearInterval(Z);Z=null}var q=o.length;for(var r=0;r<q;r++){o[r]()}}function f(q){if(e){q()}else{o[o.length]=q}}function R(r){if(typeof j.addEventListener!=b){j.addEventListener("load",r,false)}else{if(typeof K.addEventListener!=b){K.addEventListener("load",r,false)}else{if(typeof j.attachEvent!=b){I(j,"onload",r)}else{if(typeof j.onload=="function"){var q=j.onload;j.onload=function(){q();r()}}else{j.onload=r}}}}}function H(){var t=N.length;for(var q=0;q<t;q++){var u=N[q].id;if(h.pv[0]>0){var r=C(u);if(r){N[q].width=r.getAttribute("width")?r.getAttribute("width"):"0";N[q].height=r.getAttribute("height")?r.getAttribute("height"):"0";if(c(N[q].swfVersion)){if(h.webkit&&h.webkit<312){Y(r)}W(u,true)}else{if(N[q].expressInstall&&!A&&c("6.0.65")&&(h.win||h.mac)){k(N[q])}else{O(r)}}}}else{W(u,true)}}}function Y(t){var q=t.getElementsByTagName(Q)[0];if(q){var w=a("embed"),y=q.attributes;if(y){var v=y.length;for(var u=0;u<v;u++){if(y[u].nodeName=="DATA"){w.setAttribute("src",y[u].nodeValue)}else{w.setAttribute(y[u].nodeName,y[u].nodeValue)}}}var x=q.childNodes;if(x){var z=x.length;for(var r=0;r<z;r++){if(x[r].nodeType==1&&x[r].nodeName=="PARAM"){w.setAttribute(x[r].getAttribute("name"),x[r].getAttribute("value"))}}}t.parentNode.replaceChild(w,t)}}function k(w){A=true;var u=C(w.id);if(u){if(w.altContentId){var y=C(w.altContentId);if(y){M=y;l=w.altContentId}}else{M=G(u)}if(!(/%$/.test(w.width))&&parseInt(w.width,10)<310){w.width="310"}if(!(/%$/.test(w.height))&&parseInt(w.height,10)<137){w.height="137"}K.title=K.title.slice(0,47)+" - Flash Player Installation";var z=h.ie&&h.win?"ActiveX":"PlugIn",q=K.title,r="MMredirectURL="+j.location+"&MMplayerType="+z+"&MMdoctitle="+q,x=w.id;if(h.ie&&h.win&&u.readyState!=4){var t=a("div");x+="SWFObjectNew";t.setAttribute("id",x);u.parentNode.insertBefore(t,u);u.style.display="none";var v=function(){u.parentNode.removeChild(u)};I(j,"onload",v)}U({data:w.expressInstall,id:m,width:w.width,height:w.height},{flashvars:r},x)}}function O(t){if(h.ie&&h.win&&t.readyState!=4){var r=a("div");t.parentNode.insertBefore(r,t);r.parentNode.replaceChild(G(t),r);t.style.display="none";var q=function(){t.parentNode.removeChild(t)};I(j,"onload",q)}else{t.parentNode.replaceChild(G(t),t)}}function G(v){var u=a("div");if(h.win&&h.ie){u.innerHTML=v.innerHTML}else{var r=v.getElementsByTagName(Q)[0];if(r){var w=r.childNodes;if(w){var q=w.length;for(var t=0;t<q;t++){if(!(w[t].nodeType==1&&w[t].nodeName=="PARAM")&&!(w[t].nodeType==8)){u.appendChild(w[t].cloneNode(true))}}}}}return u}function U(AG,AE,t){var q,v=C(t);if(v){if(typeof AG.id==b){AG.id=t}if(h.ie&&h.win){var AF="";for(var AB in AG){if(AG[AB]!=Object.prototype[AB]){if(AB.toLowerCase()=="data"){AE.movie=AG[AB]}else{if(AB.toLowerCase()=="styleclass"){AF+=' class="'+AG[AB]+'"'}else{if(AB.toLowerCase()!="classid"){AF+=" "+AB+'="'+AG[AB]+'"'}}}}}var AD="";for(var AA in AE){if(AE[AA]!=Object.prototype[AA]){AD+='<param name="'+AA+'" value="'+AE[AA]+'" />'}}v.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+AF+">"+AD+"</object>";i[i.length]=AG.id;q=C(AG.id)}else{if(h.webkit&&h.webkit<312){var AC=a("embed");AC.setAttribute("type",P);for(var z in AG){if(AG[z]!=Object.prototype[z]){if(z.toLowerCase()=="data"){AC.setAttribute("src",AG[z])}else{if(z.toLowerCase()=="styleclass"){AC.setAttribute("class",AG[z])}else{if(z.toLowerCase()!="classid"){AC.setAttribute(z,AG[z])}}}}}for(var y in AE){if(AE[y]!=Object.prototype[y]){if(y.toLowerCase()!="movie"){AC.setAttribute(y,AE[y])}}}v.parentNode.replaceChild(AC,v);q=AC}else{var u=a(Q);u.setAttribute("type",P);for(var x in AG){if(AG[x]!=Object.prototype[x]){if(x.toLowerCase()=="styleclass"){u.setAttribute("class",AG[x])}else{if(x.toLowerCase()!="classid"){u.setAttribute(x,AG[x])}}}}for(var w in AE){if(AE[w]!=Object.prototype[w]&&w.toLowerCase()!="movie"){F(u,w,AE[w])}}v.parentNode.replaceChild(u,v);q=u}}}return q}function F(t,q,r){var u=a("param");u.setAttribute("name",q);u.setAttribute("value",r);t.appendChild(u)}function X(r){var q=C(r);if(q&&(q.nodeName=="OBJECT"||q.nodeName=="EMBED")){if(h.ie&&h.win){if(q.readyState==4){B(r)}else{j.attachEvent("onload",function(){B(r)})}}else{q.parentNode.removeChild(q)}}}function B(t){var r=C(t);if(r){for(var q in r){if(typeof r[q]=="function"){r[q]=null}}r.parentNode.removeChild(r)}}function C(t){var q=null;try{q=K.getElementById(t)}catch(r){}return q}function a(q){return K.createElement(q)}function I(t,q,r){t.attachEvent(q,r);d[d.length]=[t,q,r]}function c(t){var r=h.pv,q=t.split(".");q[0]=parseInt(q[0],10);q[1]=parseInt(q[1],10)||0;q[2]=parseInt(q[2],10)||0;return(r[0]>q[0]||(r[0]==q[0]&&r[1]>q[1])||(r[0]==q[0]&&r[1]==q[1]&&r[2]>=q[2]))?true:false}function V(v,r){if(h.ie&&h.mac){return }var u=K.getElementsByTagName("head")[0],t=a("style");t.setAttribute("type","text/css");t.setAttribute("media","screen");if(!(h.ie&&h.win)&&typeof K.createTextNode!=b){t.appendChild(K.createTextNode(v+" {"+r+"}"))}u.appendChild(t);if(h.ie&&h.win&&typeof K.styleSheets!=b&&K.styleSheets.length>0){var q=K.styleSheets[K.styleSheets.length-1];if(typeof q.addRule==Q){q.addRule(v,r)}}}function W(t,q){var r=q?"visible":"hidden";if(e&&C(t)){C(t).style.visibility=r}else{V("#"+t,"visibility:"+r)}}function g(s){var r=/[\\\"<>\.;]/;var q=r.exec(s)!=null;return q?encodeURIComponent(s):s}var D=function(){if(h.ie&&h.win){window.attachEvent("onunload",function(){var w=d.length;for(var v=0;v<w;v++){d[v][0].detachEvent(d[v][1],d[v][2])}var t=i.length;for(var u=0;u<t;u++){X(i[u])}for(var r in h){h[r]=null}h=null;for(var q in swfobject){swfobject[q]=null}swfobject=null})}}();return{registerObject:function(u,q,t){if(!h.w3cdom||!u||!q){return }var r={};r.id=u;r.swfVersion=q;r.expressInstall=t?t:false;N[N.length]=r;W(u,false)},getObjectById:function(v){var q=null;if(h.w3cdom){var t=C(v);if(t){var u=t.getElementsByTagName(Q)[0];if(!u||(u&&typeof t.SetVariable!=b)){q=t}else{if(typeof u.SetVariable!=b){q=u}}}}return q},embedSWF:function(x,AE,AB,AD,q,w,r,z,AC){if(!h.w3cdom||!x||!AE||!AB||!AD||!q){return }AB+="";AD+="";if(c(q)){W(AE,false);var AA={};if(AC&&typeof AC===Q){for(var v in AC){if(AC[v]!=Object.prototype[v]){AA[v]=AC[v]}}}AA.data=x;AA.width=AB;AA.height=AD;var y={};if(z&&typeof z===Q){for(var u in z){if(z[u]!=Object.prototype[u]){y[u]=z[u]}}}if(r&&typeof r===Q){for(var t in r){if(r[t]!=Object.prototype[t]){if(typeof y.flashvars!=b){y.flashvars+="&"+t+"="+r[t]}else{y.flashvars=t+"="+r[t]}}}}f(function(){U(AA,y,AE);if(AA.id==AE){W(AE,true)}})}else{if(w&&!A&&c("6.0.65")&&(h.win||h.mac)){A=true;W(AE,false);f(function(){var AF={};AF.id=AF.altContentId=AE;AF.width=AB;AF.height=AD;AF.expressInstall=w;k(AF)})}}},getFlashPlayerVersion:function(){return{major:h.pv[0],minor:h.pv[1],release:h.pv[2]}},hasFlashPlayerVersion:c,createSWF:function(t,r,q){if(h.w3cdom){return U(t,r,q)}else{return undefined}},removeSWF:function(q){if(h.w3cdom){X(q)}},createCSS:function(r,q){if(h.w3cdom){V(r,q)}},addDomLoadEvent:f,addLoadEvent:R,getQueryParamValue:function(v){var u=K.location.search||K.location.hash;if(v==null){return g(u)}if(u){var t=u.substring(1).split("&");for(var r=0;r<t.length;r++){if(t[r].substring(0,t[r].indexOf("="))==v){return g(t[r].substring((t[r].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(A&&M){var q=C(m);if(q){q.parentNode.replaceChild(M,q);if(l){W(l,true);if(h.ie&&h.win){M.style.display="block"}}M=null;l=null;A=false}}}}}();

