var rb = rb || {};
            rb.language = {
   "gc_pcard_iagree" : "I agree to the",
   "questions_sent" : "Your questions have been sent to our Customer Care representatives.",
   "billing_address" : "Billing Address",
   "retake_ff" : "Back to the Foundation Finder",
   "delivery" : "Shipping",
   "form" : "Form",
   "you_have_no_orders" : "You have no orders.",
   "po_reset" : "Click here to reset and assist another customer",
   "check_bal" : "Check Balance:",
   "moisturizers" : "Moisturizer",
   "email" : "Email:",
   "your_order_summary" : "Your Order Summary",
   "confirm_cc_delete" : "Are you sure you want to delete this billing information?",
   "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>.",
   "gc_learn_more" : "Learn more About Gift Cards",
   "nav_favorites" : "Favorites",
   "color_finder" : "Color Search",
   "nav_item" : "Item",
   "order_confirm_your_order" : "Your order has been received and will be processed within one (1) to two (2) business   days. When your order is shipped, you'll receive an email from us with the shipping   details. Please refer to your order number whenever contacting M·A·C Cosmetics Online Service.",
   "go_shopping" : "Go shopping!",
   "two_dry_combination" : "2 - Dry Combination Skin (dry skin - mix)",
   "replenishment.service.meta_desc" : "Replenishment Service",
   "hint" : "hint",
   "cash_on_delivery" : "Cash on Delivery (¥250 commission)",
   "chars" : "NG! characters remaining",
   "screen_name_text" : "As a MAC Pro Member, you have access to the MEMBER VOICE Bulletin Board. Use the pre-selected nickname below or enter your own nickname.",
   "sign_up_mobile_email" : "Email_Address",
   "sign_in" : "Sign in",
   "security_answer" : "Answer",
   "shades" : "Shades",
   "km" : "km",
   "the_following_added_to_cart_txt" : "The following items have been added to your cart",
   "month" : "Month",
   "lips" : "Lips",
   "the_following_removed_from_cart_txt" : "The following items have been removed from your cart",
   "previous" : "Previous",
   "account_not_name" : "If you are not |first_name|, <a href=\"account/signin.tmpl/_SUBMIT=signout.html\">sign out</a>.",
   "sign_up_pc_email" : "Email Address",
   "mobile_colon" : "Mobile:",
   "press" : "PRESS",
   "makeup" : "Makeup",
   "pro.required.country" : "Please enter your country matching your membership information.",
   "giftwrap" : "<b>Standard Gift Wrap</b></br> Box up the must-haves...tie everything up. Give your gift some style with the M·A·C gift box!",
   "gc_chkbalance_numberlabel" : "Gift Card or eGift Card Number (16 digits)",
   "address2" : "Address 2",
   "contact_us" : "Contact Us",
   "new_customers" : "New Customers",
   "mascara" : "Mascara",
   "alt.sign-in_h.gif" : "ログイン",
   "newsletter" : "Newsletter",
   "edit_personal_info_h" : "Edit personal information and passwords",
   "shipping_total_electronic_delivery" : "Electronic delivery",
   "na" : "Not applicable",
   "deaging.name" : "Anti-Aging ",
   "in_your_cart" : "in your cart",
   "order_review.page_title" : "Order Review",
   "comments" : "A question choice is required.",
   "choose_and_review_tx" : "Please review your order and click \"Submit Order\" to complete.",
   "gift_message_desc" : "You may send a free gift message without selecting gift wrap. Your message will be printed on a separate gift card.",
   "online_order" : "Online Order",
   "shipping_information" : "Shipping Information",
   "quick_shop" : "Quick shop",
   "grooming" : "Grooming",
   "chars_remaining" : "characters remaining",
   "address" : "Address",
   "store_service" : "Service at a [BRAND] Counter or Store",
   "view_all_sizes" : "VIEW ALL SIZES",
   "select_state" : "Select state",
   "may" : "May",
   "expert_tips" : "3XPERT_TIPS",
   "special_events" : "Special Events",
   "cod_commission" : "COD Commission:",
   "ingredient_concerns" : "Concerns About [BRAND] Ingredients",
   "fragrance" : "Fragrance",
   "united_states" : "United States",
   "cod" : "COD",
   "zip" : "Zip/Postal Code",
   "shipconfirm_email_subject" : "Your MAC Online Order ::TRANS_ORDER_ID:: has shipped",
   "po_csr_signout" : "csr signout",
   "gift_message" : "Gift Message:",
   "migrated_user_do_you_accept" : "Do you accept the new legal terms?",
   "view_all" : "View all",
   "customer_email_greeting" : "customer_email_greeting.copy needed for this key",
   "sign_up_postal_code" : "Postal_Code",
   "personal_information" : "Personal Information",
   "additionalinfo" : "Additional Info:",
   "eyes" : "Eyes",
   "lipgloss" : "Lipgloss",
   "order_status_0" : "Pending",
   "status" : "Status",
   "brand" : "[BRAND]",
   "forgot_pass" : "Forgot Your Password?",
   "results_range" : "Results 1 to ::max_per_page:: of ::total_results::",
   "share" : "Share this product",
   "member_info" : "Membership Information",
   "customer_service_txt" : "If you need help or would like to order by phone, call customer service at 1-800-588-0070.",
   "option" : "Option",
   "starred_password_hint" : "*Password hint",
   "no_addresses_saved" : "You have no addresses on file.",
   "cartbox.name" : "eGift Card:",
   "ec_retrieved_email_subject" : "[Recipient Name] has received the MAC Online eGift Card you sent",
   "sets_gifts" : "Sets & Gifts",
   "shipping_instructional_text" : "There are two payment methods available on [BRAND], please pay by credit card through our payment partner or by COD. Please note that for COD there is a commission charge of ¥250 for purchase less than ¥20,000 (tax included) and ¥450 for purchase of ¥20,000 or more (tax included).",
   "select_gift_card_value" : "Select a Gift Card Value",
   "po_place_an_order" : "Click here to place an order for this customer",
   "sub_total" : "Sub Total:",
   "pores" : "Pores",
   "yes" : "Yes",
   "enter_offer_code" : "Enter offer code",
   "uneven_skin_tone" : "シミ・ソバカス",
   "delivery_every_03" : "Delivery Every 60 Days",
   "1_where_purchased" : "Where can it be purchased?",
   "pass_request_header" : "If you still do not know the password",
   "inventory_status_message_7" : "SOLD OUT",
   "on" : "on",
   "postal_code" : "Zip/Postal Code",
   "sign_in_with_pw_txt" : "We have reset your password. Please login using your temporary password and you can reset your password once signed in.",
   "shop_now" : "Shop Now",
   "order_status_1" : "Pending",
   "egiftcard_you_have_been_sent" : "YOU HAVE BEEN SENT A MAC eGIFT CARD!",
   "benefit" : "Benefit",
   "select_payment_type" : "Select Payment Type",
   "video" : "VIDEO",
   "change" : "change",
   "required.first_name" : "config Please supply your first name.",
   "description" : "Name",
   "basic_regimen" : "BASIC_REGIMEN",
   "cc_expiration_date" : "Expiration",
   "order_status_7" : "Partially Shipped",
   "once_completed_email_txt" : "Once your order is completed, you'll receive an email confirmation.",
   "member_address" : "Member Address",
   "required.password" : "config Please supply your password.",
   "migrated_user_agree_txt" : "By clicking on “I Agree” below, you are acknowledging that you have read, understand, and agree to be bound by the <a class=\"lwPopupButton\" href=\"cms/customer_service/terms.tmpl\">Terms and Conditions</a> and <a class=\"lwPopupButton\" href=\"cms/customer_service/privacy.tmpl \">Privacy Policy</a> for this website.",
   "payment_options" : "Payment Options",
   "sign_in_forgot" : "Forgot_Password?",
   "gift_message_max_characters" : "XXX characters maximum",
   "order_status_12" : "Cancelled",
   "order_tracking_info_txt" : "To see more information, please select the order number. To find out about shipping information, click the inquiry number button, verify the delivery status.",
   "create_password_h" : "Create Password",
   "address_book_no_default_address_saved" : "This address is not on file. Please click on “Make This My Default Shipping Address” to designate one, or click on the Add New Address button above”",
   "gift_card" : "Gift Card",
   "locs_near" : "locations near you.",
   "terms_and_conditions" : "Terms and Conditions",
   "add_address" : "Add Address",
   "order_details" : "Order Details",
   "your_confirmation_txt" : "Your confirmation number is",
   "concern" : "肌の悩み",
   "powder" : "Powder",
   "customerservice" : "CUSTOMER SERVICE",
   "terms_agreement_end" : " for this website.",
   "japanpost_txt" : "Need help finding your postal code? <a href=\"http://www.post.japanpost.jp/zipcode/index.html\" target=\"_blank\">Click here</a> to look it up on japanpost.jp",
   "sign_up" : "Sign up to receive email about exclusive offers, Gifts with Purchase, in-store activities and special events.",
   "firstname" : "First Name",
   "coverage" : "Coverage",
   "email_address_verify" : "Email Address Verify",
   "color_group" : "カラーグループ",
   "katagana" : "Katagana",
   "four_very_oily" : "4 - Oily Skin",
   "add_gift_wrap" : "Gift Wrap",
   "state" : "State / Province",
   "address_2" : "Address 2",
   "express" : "Express",
   "without_tax" : "without tax",
   "temporary_password" : "Temporary password",
   "giftwrapdesc" : "When you select gift wrap, your entire order will be wrapped together.</br> Add gift wrap for US$4.95.",
   "your_default_shipping_address" : "YOUR DEFAULT SHIPPING ADDRESS",
   "articles" : "ビューティ アドバイス",
   "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",
   "event" : "Event",
   "birth_month" : "Birth Month",
   "conf_email-coming" : "You will receive an order confirmation within minutes by email. We also send you a shipping confirmation as soon as your order ships.",
   "pro.required.birth_month_day" : "Please enter your birth month and day matching your membership information.",
   "about_you" : "Optional Information",
   "newsletter_optin_thanks_desc1" : "Thanks!",
   "price" : "Price",
   "account_billing_choose_address" : "!Please add or choose an address at right.",
   "search_page" : "Search page",
   "finish" : "Finish",
   "ask_an_artist_external_subject" : "Ask an artist",
   "there_are" : "There are",
   "signup" : "Sign Up",
   "ship_type_description" : "This address is a:",
   "recent_inquiry_submitted" : "Thank you for submitting an inquiry regarding your password.",
   "if_you_are_not" : "If you are not",
   "dullness" : "Dullness",
   "nav_need_help" : "Need Help",
   "gc_incl_msg_note" : "A gift message can be included during checkout.",
   "faqlink.text" : "FAQ",
   "email_us" : "Email Us",
   "default_shipping_address" : "Set as my default shipping address",
   "skin_shades" : "Shades for your skintone:",
   "no_default_payment_saved" : "This information is not on file. Click here to visit your billing page.",
   "nav_address_book" : "Address Book",
   "check_bal-js" : "Check Balance",
   "shave" : "Shave",
   "quantity" : "Quantity",
   "your_gc_num" : "Your eGift Card Number:",
   "create_an_account_here" : "Track orders, receive M·A·C Messages, write reviews, save your Faves and more when you create a My M·A·C account.",
   "question" : "Please enter a comment or message.",
   "choose_and_review_txt" : "Please review your order and click \"Submit Order\" to complete.",
   "nav_welcome_back" : "Hi, Emiko | first name |",
   "err_your_hint" : "Your password hint is:",
   "pro_newsletters" : "Pro Newsletters",
   "shipping_as_billing_address" : "Use my shipping address as my billing address",
   "subtotal" : "Sub Total",
   "egc_retrieved_message" : "has retrieved the M·A·C Cosmetics Online eGift Card that you sent.  Thanks again for your order and for visiting www.maccosmetics.com. If you  should have any question or concerns, please contact an M·A·C Cosmetics Online Customer Service representative at MACTech@maccosmetics.com.  For fastest service, please refer to  your order number in any correspondence.  Best regards, M·A·C Cosmetics Online",
   "egiftcard_footer" : "Please click here for more information  about M·A·C eGift Cards.If you have any questions, email an M·A·C Cosmetics Online Customer Service representative at MACTech@maccosmetics.com or call 1-800-588-0070. Please note that M·A·C eGift Cards are only redeemable online at www.maccosmetics.com.",
   "password" : "Password",
   "required" : "Required",
   "formula" : "Formula",
   "sign_up_fname" : "First_Name",
   "make_default_payment" : "Make this my default payment option.",
   "tax_included_above" : "(Tax Included above)",
   "products" : "Products",
   "delivery_date_dropdown_default" : "Earliest available delivery",
   "nav_items" : "Items",
   "email_signup_text" : "Sign me up to hear from M·A·C Cosmetics about future products, services, events and special insider-only offers.",
   "pending" : "pending",
   "new_password" : "New Password",
   "emailus.href" : "/templates/customer_service/contact_us.tmpl",
   "retype_password" : "Re-type Password",
   "please_select" : "Please select",
   "sun" : "Body & Suncare",
   "delete" : "Delete",
   "account_resetpw_email_donotreply" : "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 ::LOSTPWEMAIL:: so we can correct this matter. We apologize for any inconvenience.",
   "one_item_in_cart" : "There is 1 item in your shopping bag.",
   "nav_sign_up" : "Newsletter Sign Up",
   "concealer" : "コンシーラー",
   "ordercomplete" : "Billing",
   "alphabetically" : "Alphabetical",
   "nav_sign_in" : "Sign in",
   "items_in_cart" : "There are ::items:: items in your shopping bag.",
   "newest_info_text" : "Entitles you to the inside info on special offers and samples, plus, you'll be the first to know about the newest products and latest looks!",
   "dear" : "Dear",
   "reorder_past_purchase" : "Re-order from past purchases",
   "upgrade_flash" : "You need to upgrade your Flash Player",
   "please_not" : "Please note: At this time we can only accept US billing and shipping addresses.",
   "when_is_your_birthday" : "When is your birthday?",
   "redness" : "",
   "sms_signup_text" : "I agree to the terms and conditions. Message and data rates may apply. You'll receive up to 5 messages per month. To opt out of futures messages at any time, test STOP to 898622 or reply STOP to any mobile message. For help, test HELP to 898622 or reply HELP to any mobile message.<br>",
   "choose_from_book_txt" : "Or choose an address from your address book",
   "driving_dir_h" : "Driving Directions",
   "email_alerts" : "Email alerts",
   "required_password" : "",
   "make_default_shipping" : "Make this address your default shipping address",
   "my_account_h" : "My Account",
   "err_address_identical" : "The Email Address fields must be identical.",
   "optin_confirm" : "Thank you, your submission has been received",
   "invoice_sent_to" : "Invoice sent to:",
   "alt.view-cart_btn.gif" : "VIEW CART",
   "promo_message" : "Purchase $30.00 more to qualify for FREE Standard Shipping.",
   "update" : "Change Shipping Date",
   "works_well_with_tab" : "Works well together",
   "about_mail_magazine" : "About E-mail Newsletter",
   "pass_request_contact" : "If you have any questions, please contact customer service. 1.800.588.0070.",
   "next" : "Next",
   "gc_chkbalance_remaining" : "Remaining Balance:",
   "enter_your_email" : "Enter your email, then",
   "foundation_strength" : "Neutral Undertone",
   "r_u_reg" : "Are you a Registered Member of [BRAND]?",
   "inventory_status_message_3" : "COMING SOON",
   "brows" : "アイブロー",
   "shipping_total_overnight" : "Overnight Shipping",
   "off" : "off",
   "account_landing_no_default_address_saved" : "This address is not on file. <a href = \"account/address_book\">Click here</a> to visit your address book.",
   "orderstatus" : "Order status",
   "email_promotions" : "Yes, I would like to receive alerts from [BRAND] Online about new products and exclusive shopping offers.",
   "add_gift_wrap_label" : "Gift wrap this order. (Gift wrap charge is $3.00)",
   "enter_cc_year" : "Please supply the year when your credit card expires.",
   "edit_payment_option" : "Edit Payment Option",
   "verify_email_address_starred" : "*Verify Email Address",
   "po_email_address" : "Email Address",
   "delivery_address" : "Shipping Address",
   "starred_first_name" : "*First Name",
   "returns" : "Returns",
   "enter_email" : "In regard to verification, please input email address once more.",
   "faqlink.href" : "/cms/customer_service/faqs.tmpl",
   "migrated_user_txt" : "Please note that our <a  class=\"lwPopupButton\" href=\"cms/customer_service/terms.tmpl \">Terms and Conditions</a> and <a class=\"lwPopupButton\"  href=\"cms/customer_service/privacy.tmpl \">Privacy Policy</a> may have changed since you last visited this site.",
   "ask" : "Ask an Expert",
   "discount" : "Discount:",
   "order_is_not_gift_wrapped" : "Order is not gift wrapped",
   "bestseller" : "Bestsellers",
   "phone_requirements" : "<em>One phone number is required</em>",
   "aug" : "Aug",
   "benefit_priority" : "Product Features",
   "building" : "Building name:",
   "mobile_phone" : "Mobile Phone",
   "customer_service_h" : "Customer Service",
   "order_is_gift_wrapped" : "Order is gift wrapped",
   "last_name" : "Last Name",
   "shipping_total" : "Shipping",
   "estimated_total" : "Estimated Total",
   "city" : "City",
   "yes_cap" : "Yes",
   "gift_wrapped" : "Will this order be gift wrapped?",
   "free" : "Free",
   "order_history" : "Order History",
   "pro.required.postal_code" : "Please enter the zip/postal code matching your membership information.",
   "store-locator_nav" : "Stores",
   "sign_up_header" : "Sign Up now!",
   "delivery_time" : "Delivery Time",
   "enter_cc_month" : "Please supply the month when your credit card expires.",
   "nav_account_profile" : "Account Profile",
   "limited_edition" : "Limited Edition",
   "shipmethod_option_electronic_delivery" : "Electronic delivery",
   "email_gifts_instore" : "Yes, I would like to receive email about Gifts With Purchase, In-Store activities and Special Events.",
   "features" : "Features",
   "password_verify" : "Verify Password",
   "account_landing_no_default_payment_saved" : "This information is not on file. <a href =\"account/payment_info\">Click Here</a> to visit your billing information.",
   "your_order_txt" : "Your order is not complete until you click 'Submit Order'.",
   "tax" : "Tax",
   "your_address_colon" : "Your Address:",
   "shipconfirm_footer_note" : "If your order contains backordered items, your credit card charges will reflect only the items shipped today.",
   "personal_msg" : "Gift Message",
   "delivery_every_01" : "Delivery Every 30 Days",
   "based_on" : "Based on your search criteria, there are",
   "gc_chkout_deliveredfree" : "*Delivered for free via email",
   "tracking_link" : "tracking link",
   "back_to_search" : "Back to Search",
   "account_landing_no_default_address_saved_macpro" : "This address is not on file. <a href = \"macpro/members/address_book/index.tmpl\">Click here</a> to visit your address book.",
   "shipping_total_express" : "Express Shipping",
   "order_confirmation" : "Order Confirmation",
   "skincare" : "Skincare",
   "pc_email_address" : "Email Address",
   "nav_my_account" : "My Account",
   "checkout_billing_no_default_payment_saved" : "You have no default payment method on file. Please choose one from the dropdown menu or create a new one by clicking on Add New Payment Method.",
   "payment_method_colon" : "Payment Method:",
   "click_here" : "click here.",
   "choose" : "Choose",
   "delivery_time_dropdown_default" : "Earliest available delivery",
   "global_search" : "Search",
   "expiration" : "Expiration: [mm/yy]",
   "shipping" : "Shipping",
   "screen_name" : "MEMBER VOICE NICKNAME",
   "loading" : "Loading",
   "customer_email_salutation" : "customer_email_salutation.copy needed for this key",
   "membership_id" : "Membership ID:",
   "po_reset_customer" : "Reset customer",
   "address_book" : "Address Book",
   "select_delivery_time" : "Select a delivery time",
   "orders" : "Orders",
   "skin_type" : "Skin Type",
   "shade" : "Shade:",
   "no_favorites_message" : "You have no favorites. <br><br>To add products you like for future reference, simply click “add to favorites” button on the product page.",
   "dec" : "Dec",
   "personalized_message_txt" : "Include a personalized message with your order. Your message will be printed on a gift card. You may may send a message without selecting gift wrap.",
   "beauty_advice" : "Beauty Advice and Product Application Tips",
   "address_1" : "Address 1",
   "face" : "Face",
   "to" : "To:",
   "store_loc_h" : "Store & Event Locator",
   "group" : "home",
   "street_addr" : "Street Address:",
   "home" : "Home",
   "cartbox.email" : "E-mail address to receive",
   "country" : "Country",
   "order_status_6" : "Cancelled",
   "member_exists_signin" : "Please signin to your existing member account.",
   "payment" : "Payment Method",
   "password_starred" : "*Password",
   "forgot_pw" : "Forgot your password?<br/>Enter your email, <a id=\"forgot-password\" href=\"javascript:void();\">then click for a hint</a>.",
   "pc_colon" : "PC:",
   "start_addr" : "Please enter your starting address",
   "card_number" : "Card Number:",
   "fields_required" : "Required",
   "po_new_user" : "new user",
   "continue_shopping" : "Continue Shopping",
   "pls_choose_password" : "Please choose a password.",
   "registration_update_info" : "Please review your information below and add any missing information.",
   "edit_add" : "Edit/Add",
   "gifts" : "Gifts",
   "po_currently_signed_in_csr" : "<i>You are currently signed in with csr account</i>",
   "password_criteria_txt" : "The password is case-sensitive. Please enter 6-20 characters.",
   "new_password_verify" : "Re-enter New Password (for confirmation)",
   "most_recent_order" : "Most recent order:",
   "confirm_delete" : "Are you sure you want to delete this address?",
   "macpro_pass_request_contact" : "If you have any questions, please contact customer service. In the US, please call 1-800-866-6464; for Canadian customers, please call 1-800-387-6707 ext 8555.",
   "you_saved" : "You saved",
   "skin_concern" : "Skin Concerns",
   "or" : "Or:",
   "step1_cleanse" : "Step 1: Cleanse",
   "err_reset_password" : "Still can't remember? <a id=\"pwd-reset\" href=\"javascript:void(0);\">Click here</a> to reset your password.",
   "order_status" : "Order Status",
   "cancel" : "Cancel",
   "address_as_delivery" : "Use this address as my delivery address.",
   "member_registration_email_subject" : "Welcome to M·A·C Pro Online",
   "how_to_use" : "Use",
   "account_landing_no_address_saved" : "This address is not on file. Click here to add it.",
   "pass_sent_header" : "Password has been sent",
   "return_to_site" : "You can return to the site and sign in at this location:",
   "benefits" : "Product Features",
   "newsletters" : "Newsletters:",
   "neutral_undertone" : "Neutral_Undertone",
   "spam" : "SPAM filters and ISPs",
   "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 Options",
   "offercode" : "Offer Code",
   "enter_firstname" : "Please enter your First Name",
   "order_confirm_order_number" : "Order number",
   "example" : "example: username@network.com",
   "other_address" : "OTHER ADDRESSES",
   "err_please_sign_in" : "Please sign in below.",
   "tax_information" : "Sales Tax",
   "product_concerns" : "Concerns About [BRAND] Products",
   "add_payment_method_h" : "Add Payment Method",
   "alt.sign-in_btn.gif" : "Sign in",
   "view_past_purch" : "View All Past Purchases",
   "view_details" : "View details",
   "order_past_purchases" : "Re-order from Past Purchases",
   "nav_not_name" : "(not #|first_name|?)",
   "alt.ecard_text_how.gif" : "NOW IS A GREAT TIME TO TREAT YOURSELF TO A GIFT FROM MAC COSMETICS ONLINE.",
   "lost_pw_email_subject" : "Your Customer Service Request",
   "denotes_required_field" : "",
   "po_session_open_for" : "Session open for",
   "select_country" : "Select country",
   "print" : "Print",
   "street_block" : "Street/Block No.:",
   "business" : "Business",
   "category" : "カテゴリー",
   "alt.ecard_has_been_retrieved.gif" : "THE MAC eGIFT YOU SENT WAS RETRIEVED",
   "newsletter_optin_thanks_desc2" : "We've updated your communication preferences.",
   "locator_no_results" : "Your search returned no results.",
   "checkout_without_account" : "Wish to continue without creating an account?",
   "nav_reorder" : "Re-order from Past Purchases",
   "change_pass_text" : "We have sent you a temporary password, but in order to keep your personal information safe, please create a new password.",
   "male" : "Male",
   "account_landing_no_default_payment_saved_macpro" : "This information is not on file. <a href =\"macpro/members/payment_info/index.tmpl\">Click Here</a> to visit your billing information.",
   "you_have_no_past_purchases" : "You have no past purchases.",
   "samples" : "Can you send me samples?",
   "bill_to" : "Bill to:",
   "find_stores" : "Find Stores and Events in your area",
   "female" : "Female",
   "payment_information" : "Billing Information",
   "shipmethod_option_overnight" : "Overnight",
   "pls_password_hint" : "Please enter a password hint",
   "add_new_address" : "Add New Address",
   "security_question_colon" : "Security Question:",
   "sign_up_h" : "Sign Up Now with [BRAND].com",
   "2_how_delivered" : "How will it be delivered?",
   "all" : "All",
   "err_password_identical" : "The Password fields must be identical.",
   "house_number" : "House Number",
   "customer_service" : "Customer Service",
   "shop_all_shades" : "Shop all shades",
   "account_profile" : "Account Profile",
   "bank_name" : "",
   "add_new_payment_option" : "Add New Payment Option",
   "prod_name" : "Product Name",
   "gift_wrapped_yes" : "This order will be gift wrapped.",
   "shipping_carrier" : "Shipping Carrier:",
   "po_yes" : "yes",
   "delivery_address_colon" : "Deliver to:",
   "err_verify_password" : "Confirm New Password",
   "your_cart_empty_txt" : "Your cart is currently empty.",
   "delivery_day_info" : "Have your order delivered as soon as it’s ready, or choose your delivery date.<br>Monday to Saturday, subject to availability and excluding public holidays.",
   "skin_types" : "Skin Types",
   "cert_pin" : "Certificate PIN",
   "order_status_5" : "Backordered",
   "shipmethod_option_express" : "Express",
   "remove" : "Remove",
   "migrated_user_header" : "Legal Acceptance",
   "enter_phone" : "Please enter a valid Phone Number.",
   "member_exp_date" : "Expiration Date:",
   "no" : "no",
   "sign_up_clin_emails" : "Sign me up for [BRAND] emails",
   "order" : "About my Order",
   "gift_invoice" : "Invoice",
   "mobile_email_address" : "Mobile Email Address",
   "store_loc_bc" : "Store & Event Locator",
   "limited_to_united_states" : "Addresses limited to the United States",
   "tools_gifts" : "Tools & Gifts",
   "total" : "Total",
   "account.migrated-user.page_title" : "Add Mobile Key",
   "po_become_customer" : "Become Customer",
   "gift_wrap_restrictions" : "",
   "order_date" : "Order Date",
   "shipping_address" : "Default Shipping Address:",
   "in_providing_your_email" : "In providing your email address you are agreeing to receive email communication from [Brand] Online. If you do not wish to receive such communication via email, please un-tick this box.",
   "alt.add_to_bag_btn.gif" : "Add to Cart",
   "password_criteria" : "case sensitive, 6-12 characters",
   "max_purchase_policy" : "Maximum Purchase Policy",
   "register_pro_post_form_paragraph_2" : "Post form para 2",
   "cartbox.to" : "Gift Shipping",
   "pass_need_to_reset" : "Since you have forgotten your password, we will need you to reset it.",
   "select_cc_type" : "Please select the type of credit card.",
   "search_results" : "Search Results",
   "pro_renewal_alert" : "Your M·A·C Pro account expires on [exp date]. Renew today for another year of membership.",
   "one_offer_only" : "One offer code per order",
   "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 “Previously Purchased\" button below.",
   "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.",
   "select_payment_message" : "*Select a payment method:",
   "po_start_shopping_as" : "Start shopping as:",
   "phone" : "Day Telephone",
   "tracking_number" : "Tracking Number",
   "exfoliators_masks" : "Exfoliators & Masks",
   "concerns" : "What are your main skincare concerns?<br>(check your top three concerns)",
   "pass_request_txt" : "Please click submit so that we may email you a link to reset your password. Please note, for security reasons, any credit card information you have saved with us will be deleted.",
   "product" : "Product",
   "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.",
   "no_items_in_cart" : "There are currently no items in your shopping cart",
   "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.",
   "order_status_3" : "Error with Order",
   "starred_last_name" : "*Surname",
   "required.first_name_alternate" : "config Please supply your first name informal.",
   "shipping_discount" : "Shipping Discount:",
   "customer_phone" : "Customer Phone Number",
   "terms-conditions_nav" : "Terms & Conditions",
   "still_cant_remember" : "Still Can't Remember?",
   "shopping_cart_h" : "Shopping Cart",
   "day" : "Day",
   "stores_enter" : "For the [BRAND] counters nearest you, please enter your city and state/territory or postal code.",
   "enter_city" : "Please enter your City",
   "payment_provider" : "Credit Card (Visa or Mastercard)",
   "email_address_starred" : "Email Address",
   "my_message" : "My Message or Comment",
   "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.",
   "hazmat.item" : "<font color=\"#888888\">Cannot ship to APO/FPO address.</font>",
   "gc_chkbalance_enternumpin" : "Enter the card number and PIN below and click the check balance button to view your balance.",
   "save_results" : "Save Results to Your Profile",
   "order_gift" : "Is this a gift? YES",
   "view_bag" : "View Cart",
   "nav_orders" : "Orders",
   "purchase_one_time" : "Purchase One Time",
   "help_or_phone_order" : "If you need help or would like to order by phone call customer service at 1-800-464-8585",
   "order_confirm_email_subject" : "Thank you for placing your order at MAC Online.",
   "confirmemail" : "Re-type Email Address",
   "mobile_opt_in_email_signup" : "M&middot;A&middot;C goes mobile! Share your cell phone number to receive updates.",
   "password_hint" : "Password Hint",
   "po_registered_user" : "registered user",
   "recruiting" : "recruiting.copy needed for this key",
   "pc" : "(PC)",
   "size" : "Size",
   "do_not_save_credit_card" : "I do not want to save my credit card details",
   "signout" : "Sign Out",
   "mobile_operator" : "Service Provider",
   "ec_activation_email_subject" : "The eGift Card you purchased at MAC Cosmetics Online has been sent to Recipient. Below is a copy for your records.",
   "exp_date" : "",
   "faq" : "Frequently Asked Questions.",
   "enter_password" : "Enter Password",
   "change_pass_header" : "Password recovery",
   "shipping_method" : "Shipping Method",
   "po_no" : "no",
   "added_to_shopping_bag" : "was added to your shopping bag.",
   "yes_newsletter_txt" : "I'd like to hear from M·A·C Cosmetics Online about products, services, events and special insider-only offers.",
   "print_complete_order" : "Print Order Details",
   "customer_email_we_received" : "customer_email_we_received.copy needed for this key",
   "pin" : "Certificate PIN",
   "notsure" : "Not Sure",
   "cert_num" : "Certificate Number",
   "valid_cc" : "Please note: 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.",
   "inventory_status_message_2" : "Out of stock - ships soon!",
   "enter_zip" : "Enter zip",
   "yes_mobile_newsletter_txt" : "Yes! I would like to receive a mobile newsletter about products, online offerings, and events.",
   "bronzer" : "bronzer",
   "gc_purchase_with_giftcard" : "Amount charged to gift card",
   "womens" : "レディース",
   "requiredfields" : "Denotes required fields",
   "enter_lastname" : "Please enter your Last Name.",
   "no_dashes_spaces" : "No spaces or dashes",
   "enter_cc" : "Please enter your credit card number with no spaces or dashes",
   "deaging" : "Anti-Aging",
   "order_review" : "Order Review",
   "other" : "Other",
   "customer_email_thankyou" : "customer_email_thankyou.copy needed for this key",
   "cc_charge" : "Amount charged to credit card",
   "customer_name" : "Customer Name",
   "delivery_every_02" : "Delivery Every 45 Days",
   "contact_phone" : "Contact Phone Number",
   "store_loc_title" : "Stores & Events > [BRAND]",
   "pro_international_checkout" : "Please note that international Pro members cannot purchase online. Please visit your local store to purchase M·A·C products. <a href=\"locator/index.tmpl\">Find a store near you</a>.",
   "account_billing_no_default_payment_saved" : "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.",
   "alt.sign-up_btn.gif" : "登録する",
   "kangi_kana_ascii" : "Kanji / Kana / ascii",
   "gc_pcard_terms" : "Gift Cards Terms and Conditions.",
   "one_very_dry_to_dry" : "1 - Very Dry to Dry Skin",
   "checkout_shipping_no_default_address_saved" : "You have no default address on file. Please choose an address from the address book or create a new one by clicking on Add New Address.",
   "shippinginfo" : "Shipping & Handling",
   "select_2nd_day" : "Please select Second Day as your shipping method.",
   "order_status_4" : "Delivered",
   "err_enter_address" : "Please enter your address.",
   "gc_chkbalance_pinlabel" : "Gift Card or eGift Card PIN (8 digits)",
   "your_email" : "Your email*",
   "signin_pro_clicktoregister" : "click here to register.",
   "items" : "items",
   "payment_info" : "Billing Information",
   "select_default_store" : "My M·A·C Store",
   "three_oily" : "3 - Combination Oily Skin",
   "add_to_favorites" : "Add to Favorites",
   "lookup_address" : "Lookup Address",
   "shipmethod_option_second_day" : "Second Day",
   "delivery_every_06" : "Delivery Every 120 Days",
   "order_status_2" : "Shipped",
   "shipping_total_second_day" : "Second Day Shipping",
   "your_address" : "Address",
   "request" : "Request",
   "create_an_account" : "Create an Account",
   "cartbox.message" : "Gift Message",
   "account_resetpw_email_save_msg" : "Please save this message for future reference. For your security, please note that when you sign in, you will be prompted to reset your password. Any stored credit card information will be deleted. If you need assistance, please call customer service at 1-800-588-0070.",
   "pls_confirm_password" : "Please confirm your password.",
   "your_sex" : "Your sex",
   "name" : "Name:",
   "phone_2" : "Evening Telephone",
   "terms_agreement_and" : " and ",
   "details" : "details",
   "customer_email_will_review" : "customer_email_will_review.copy needed for this key",
   "egift_check_txt_04" : "eGift Card PIN (8 digits)",
   "card_type" : "Card Type:",
   "no_past_purchases" : "You have no past purchases",
   "purchase_with_egift" : "Purchase with eGift Card",
   "pass_sent_txt" : "E-mail has been sent to the address below to let you know the temporary password. Next time you log in with your temporary password, please set a new password.",
   "shipping_total_standard" : "Standard Shipping",
   "change_pass_thanks_header" : "You have reset your password",
   "inquiry_regarding" : "Questions",
   "no_items" : "There are currently no items in your shopping cart",
   "po_phone_order" : "Phone Order",
   "cl_online_order" : "My [BRAND] Online Order",
   "privacy_policy" : "Privacy Policy",
   "screenname" : "Screenname:",
   "mobile" : "(Mobile)",
   "select_one" : "Choose one",
   "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:",
   "sign_up_email" : "SIGN UP FOR EMAIL",
   "alerts" : "Alerts:",
   "in_your_bag" : "in your shopping bag.",
   "verify_password" : "Verify Password",
   "order_not_gift" : "Is this a gift? NO",
   "checkout_guest" : "Click here to checkout as a Guest.",
   "your_default_payment_info" : "YOUR DEFAULT PAYMENT METHOD",
   "edit_update_shipping" : "Edit/Update Shipping",
   "no_favorites_signin" : "If you are a member of [BRAND] Online and you have saved favorites, <a href=\"country_chooser.tmpl\">click here</a> to sign in and see them.",
   "read_reviews" : "Read reviews",
   "po_phone_number" : "Phone Number",
   "err_verify_email" : "Please verify your email address.",
   "reenter_password" : "Please re-enter your password",
   "fields_are_required" : "Denotes required fields",
   "sign_up_clin_store" : "Yes, I would like to receive email about Gifts with Purchase, In-Store activities and special events.",
   "order_num" : "If available please provide your order no.",
   "verify_email_address" : "Verify Email Address",
   "optin_error" : "Sorry, there was an error submitting your request.",
   "delivery_every_05" : "Delivery Every 90 Days",
   "web_site" : "Questions about our Web site",
   "search_again" : "Search again",
   "email_macpro_signup_text" : "M·A·C Pro Emails<br> I'd like to hear from M·A·C PRO Membership about my membership, M·A·C collections, M·A·C PRO events, M·A·C PRO Online and special Pro Member-only offers.",
   "email_address" : "Email Address",
   "foundation_desc" : "Stay True Make up<br />Oil Free Formula.",
   "checkout_message_instructions" : "* PLEASE NOTE: English language characters are accepted only. Use of other language characters cannot be accepted and will result in processing delays.",
   "newest" : "Newest",
   "save_message" : "Please save this message for future reference.",
   "earliest_possible" : "Earliest available delivery",
   "gc_chkout_redemptionamt" : "Gift Card Redemption Amount",
   "edit_this_address" : "Edit this address",
   "password_or_reg" : "My [BRAND] Registration or Password",
   "signin_pro_registration" : "If you are a M·A·C PRO member who has previously signed in before on M·A·C or M·A·C Pro sites, please sign in under \"Already Registered\" with your email and password. If you are a member and would like to register to receive your special PRO privileges",
   "egiftcard_sender_copy" : "The M·A·C eGift Card you purchased at http://www.maccosmetics.com has been sent to ::GIFTCARD_TO::. Below is a copy for your records.",
   "colour_group" : "C0lour_Group",
   "cm_opt_anon_status" : "cm_opt_anon_status.copy needed for this key",
   "returning_customers_checkout_signin" : "Have a My M·A·C, M·A·C Pro account or written a review before? Existing Customers and Pro members, sign in with your email address and password here.",
   "all_shades" : "All Shades",
   "pro_discount" : "Pro discount is available on select items",
   "lastname" : "Last Name",
   "close" : "Close",
   "become_member" : "Become a member of [ BRAND ] Online!",
   "shipconfirm_email_heading" : "YOUR ORDER FROM M·A·C COSMETICS ONLINE HAS SHIPPED",
   "mail_magazine" : "About E-mail Newsletter",
   "select" : "Please select",
   "po_reset_are_you_sure" : "Are you sure you want to sign out as this customer?",
   "store" : "Store:",
   "shipped" : "shipped",
   "enter_state" : "Please select the state for this address.",
   "delivery_options" : "Delivery Method",
   "zipcode" : "Postal Code",
   "thank_you" : "Thank you",
   "residence" : "Residence",
   "message" : "Please limit the length of the giftcard message to 215 characters.",
   "shop" : "Shop",
   "cc_number" : "Credit Card Number:",
   "last_ordered" : "Last ordered",
   "deliver_to_different_address" : "Deliver To A Different Address",
   "first_name" : "First Name",
   "chemistry" : "chemistry",
   "shipmethod_option_standard" : "Standard",
   "nav_payment_info" : "Billing Information",
   "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.",
   "golden_undertone" : "Golden_Undertone",
   "account_landing_no_payment_saved" : "This information is not on file. Click here to add it.",
   "add_new_payment_option_h" : "Add Payment Method",
   "no_address_lookup" : "There is no addresses found.",
   "passwordhintdesc" : "Enter a question or phrase that will help remind you of your password.",
   "thank_you_for_your_order" : "Thank You for Your Order",
   "order_number" : "Order Number",
   "promocode" : "Promotional Code",
   "passwordhint" : "Password Hint",
   "sort_by" : "Sort by",
   "tools" : "Tools",
   "customer_service_txt_pro" : "If you need help or would like to order by phone, call customer service at 1-800-866-6464.",
   "billing_address_header" : "BILLING ADDRESS",
   "mobile_opt_in_terms" : "I agree to the terms and conditions. Message and data rates may apply. You'll receive up to 5 messages per month. To opt out of futures messages at any time, test STOP to 898622 or reply STOP to any mobile message. For help, test HELP to 898622 or reply HELP to any mobile message.<br>",
   "foundation" : "Foundation",
   "no_cap" : "NO",
   "gift" : "Gift",
   "gift_wrapped_no" : "This order will not be gift wrapped.",
   "returning_customers" : "Returning Customers",
   "required.last_name_alternate" : "config Please supply your last name informal.",
   "welcome" : "Welcome,",
   "prefecture" : "Prefecture:",
   "checkout_billing_choose_address" : "Please choose your billing address.",
   "retrieve_egiftcard_now" : "RETRIEVE YOUR EGIFT CARD NOW!",
   "signin_return_customer" : "Have a My M·A·C, M·A·C Pro account or written a review before? Existing Customers and Pro members, sign in with your email address and password here.",
   "describe_skin_tone" : "How would you describe your skin tone?",
   "nav_sign_out" : "Sign Out",
   "customer_type" : "I am a:",
   "select_delivery_date" : "Select a delivery day",
   "edit" : "Edit",
   "pink_undertone" : "Pink_Undertone",
   "your_password_is" : "Your temporary password is:",
   "delivery_date" : "Delivery Date",
   "ask_an_artist_internal_subject" : "Ask an artist",
   "from" : "From:",
   "shade_name" : "Shade name:",
   "order_total" : "Order Total:",
   "gender" : "Gender",
   "oct" : "Oct",
   "shipconfirm_email_intro" : "We are pleased to inform you that your order ::NUMBER:: has been processed and shipped. For your convenience, we are enclosing the details of your order.",
   "required.customer_type.contact_us" : "Please tell us if you are a U.S., Canadian or Pro customer.",
   "shipconfirm_email_tracking" : "The tracking number is:",
   "signin" : "Sign In",
   "invoices_will_be_sent" : "Note: invoices for gifts will be sent to this address.",
   "register_pro_post_form_paragraph_1" : "Post form para 1",
   "required.last_name" : "config Please supply your last name.",
   "filter_by" : "Filter by",
   "prod_questions" : "Product Questions",
   "re-enter_password_starred" : "*Re-enter Password",
   "edit_address_h" : "Edit address",
   "more" : "More",
   "year" : "Year",
   "pro_advice" : "Professional Beauty Advice and Makeup Tips",
   "expires" : "Exp. Date",
   "billing" : "Billing",
   "meet_your_match" : "conf Meet Your Match!<br/>Here's the foundation we recommend for your skin.",
   "birth_day" : "Birth Day",
   "create_an_account_h" : "Create an Account",
   "add_to_bag" : "Add to Cart",
   "frequency" : "Frequency",
   "gift_invoice_desc" : "Your gift invoice will be sent to the following address:",
   "deliveredforfree" : "*Delivered for free via email",
   "giftcard" : "Redeem a Gift Card",
   "delivery_and_options" : "Delivery and Options",
   "foundation_tone" : "Moderately Fair",
   "my_account" : "My Account",
   "regarding" : "My Question is regarding",
   "for" : "for:",
   "mar" : "Mar",
   "skin_tone" : "Skin Tone",
   "payment_method" : "Payment Method",
   "city_town" : "City",
   "address1" : "Address 1",
   "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 ",
   "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>",
   "dry_combination" : "2 - Dry Combination Skin (dry-combination skin)",
   "edit_address_book_link" : "Click here to change your address.",
   "hazmat_note" : "Please note that a signature may be required upon delivery for Overnight or 2nd Day shipments.",
   "help" : "Help",
   "in_store_service" : "In-Store Service",
   "return_to_site_sign_in" : "You can return to the site and sign in at this location:",
   "required.password2" : "config Please confirm your password.",
   "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",
   "undertone" : "not sure of your undertone?",
   "enter_email_checkout_signin" : "Start by entering your email address, and we’ll walk you through the checkout process.",
   "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" : "QTY",
   "delivery_every_04" : "Delivery Every 75 Days",
   "sep" : "Sep",
   "customerservice.landing.name" : "Contact Us"
};
        
            rb.brand = {
   "mac_aids_fund_copy" : "Supporting men, women, and children affected by HIV/AIDS globally, the M·A·C AIDS Fund was established by M·A·C...",
   "pro_acct_sitechange_usca" : "As a registered Pro Member, you will have access to view and shop all M·A·C Products, including Pro Products, using your discount here at M·A·C Online.  <br><br>To access Pro exclusive content including artistry, trends, Pro events and Pro Membership details, <a href=\"macpro/index.html\" target=\"_blank\">visit macpro.com</a>.",
   "alt.pnav_security_off.gif" : "Security",
   "alt.h_creative_influence.gif" : "Creative Influence",
   "packaging_4" : "Packaging No.4",
   "chrs_remaining_singular" : "character remaining",
   "alt.h_search_results_430x28.gif" : "Search Results",
   "corporate_limits" : "For corporate gift purchases and services that exceed our maximum purchase policy, please call 1-800-588-0070.",
   "country_it" : "Italy",
   "btm_return_packaging11" : "Empty Lipstick Tube",
   "alt.mac_stores.gif" : "MAC Stores",
   "alt.department_stores.gif" : "Department Stores",
   "alt.h_fav_mac_prod_prod.gif" : "Favourite M·A·C Product (M·A·C Pro)",
   "alt.btn_view_collectin.gif" : "View Collection",
   "locator_no_results" : "Your search returned no results.",
   "lipstick_amplified_creme" : "Ultra-creamy. Quietly shiny. Colour-packed! Hi-res: hi-impact.",
   "no_previously_purchased_items" : "No previously purchased items.",
   "prolanding_student_membership" : "Support for the Makeup Professional comes in many ways at M·A·C and the M·A·C PRO Student Program is just the beginning of our relationship with any new Makeup Professional. It is ONLY open to students attending a school or institution of Makeup Artistry, Cosmetology, Hairstyling, Aesthetics, Photography, Fashion Styling or Performing Arts. This paid-for membership program offers students a direct link to the M·A·C world as well as member-exclusive benefits. M·A·C PRO Student Members must attend an eligible school or institution of Makeup Artistry or the Performing Arts. It's the beginning of M·A·C's dynamic relationship with any makeup professional just starting out.<br><br> <b>How do I become a M·A·C PRO Student Member?</b><br> For information about program eligibility and details about membership benefits please visit <a href=\"macpro/index.html\"><font color=\"#FFFFFF\">M·A·C PRO</font></a> or contact 1-877-553-5536 (available Monday through Friday from 9:00am to 5:00pm) or email <a href=\"mailto:promembership@maccosmetics.com\"><font color=\"#FFFFFF\">M·A·C PRO Membership</font></a>.",
   "powder_blush_matte" : "Flat matte finish. Goes on lightly. Builds well. Blends with the skin.",
   "describe_question" : "Please, describe your question or concern in as much detail as possible.",
   "hair_color" : "Hair Colour",
   "gc_check_gc_balance" : "Checking balance on M·A·C Gift Cards",
   "dropdown_mac_store" : "Service at a M·A·C Counter or Store (address required)",
   "alt.pro_stores.gif" : "PRO Stores",
   "10_backordered_items" : "Backordered Items",
   "5_recycling" : "Recycling with Back To MAC",
   "gnav_items_in_bag" : "items",
   "alt.viva_glam_products.gif" : "VIVA GLAM Products",
   "alt.h_my_fav_products.gif" : "My Favorite Products",
   "alt.btn_mascara_finder.jpg" : "Mascara Finder",
   "added_to_faves" : "was added to your faves.",
   "pink_beige" : "Pink Beige",
   "of" : "of",
   "alt.btn_next_step.gif" : "Print Form & Next Step",
   "powder_blush_frost" : "Iridescent, lightly shimmering colour. Adds highlights: provides a frosted \"bloom\" to the cheeks.",
   "alt.pnav_asia_australia_off.gif" : "ASIA & AUSTRALIA",
   "alt.btn_view_location_results.gif" : "View Location Results",
   "start_address" : "start address",
   "previous" : "Previous",
   "alt.pnav_top_inquiries_off.gif" : "Top Inquiries",
   "alt.pnav_chat_live_200x12_off.gif" : "Chat Live with an Artist",
   "blonde" : "Blonde",
   "trendy" : "Trendy",
   "like_to_hear_from_mac" : "I’d like to hear from M·A·C Cosmetics Online about products, services, events and special insider-only offers.",
   "registered_user_pro" : "YES, I AM A REGISTERED M•A•C PRO USER",
   "makeup_services_ays" : "A product demonstration introducing you to some of  the product techniques that can add to, and enhance your...",
   "registered_user" : "YES, I'M A REGISTERED M·A·C USER",
   "pro_acct_renewal_amount" : "Amount",
   "btm_return_packaging4" : "Empty Cream Colour Base",
   "pro_acct_renewal_info" : "Auto renewal keeps your membership current and allows   you to continue to enjoy the benefits of your M·A·C   PRO membership without interruption. You can choose to   be charged the annual fee each year automatically when   your membership expires.",
   "alt.pnav_shipping_off.gif" : "Shipping",
   "alt.h_emailusquestion.gif" : "Email M·A·C",
   "alt.gnav_whats_new_off.gif" : "What’s New",
   "alt.pnav_returns_exchanges_off.gif" : "Returns & Exchanges",
   "was_added_to_your_favourites" : "was added to your favourites.",
   "country_tw" : "Taiwan",
   "search_site" : "Search the site",
   "pro_acct_membership_expire" : "Your membership will expire on:",
   "alt.h_finish.gif" : "Finish",
   "search_no_results" : "Your search for \"QUERY\" returned no results.",
   "makeup_services_lips" : "A production demonstration in which the classic  neutral lip or more dramatic lip is created. Defining techniques and how  to use colour.",
   "shoptogether_landingpage_meta_title" : "Try ShopTogether on MAC",
   "alt.pnav_featured_goodbyes_off.gif" : "Featured Goodbyes",
   "alt.h_discontinued_prods.gif" : "Goodbyes",
   "member_exp_date" : "Expiration Date:",
   "alt.pnav_viva_glam_off.gif" : "VIVA GLAM",
   "alt.pnav_shipping_information_off.gif" : "Shipping Information",
   "3_careers_artist" : "Careers As a MAC Artist",
   "alt.fnav_legal_off.gif" : "Legal/Privacy",
   "alt.h_issue.gif" : "Issue or Question",
   "dark_brown" : "Dark Brown",
   "country_za" : "South Africa",
   "alt.pnav_programs_services_off.gif" : "Programs and Services",
   "red" : "Red",
   "isp_msg" : "PLEASE NOTE: Your Internet Service Provider (ISP) or email client may be using filters to block email that may affect the receipt of email from maccosmetics.com. 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. This will ensure that correspondence from maccosmetics.com will be accepted. Additional Information is available in our FAQ section above. If you have further questions, please contact us at 1-800-588-0070.",
   "wait_for_operator_to_respond" : "Please wait for a site operator to respond.",
   "dramatic" : "Dramatic",
   "alt.h_list_your_preferred_shades.gif" : "List Your Preferred Shades",
   "unsubscribe_email_address" : "Email Address",
   "gc_redeeming_gc_at_store" : "Redeeming M·A·C Gift Cards at  MAC Retail Store",
   "event_name" : "Event Name",
   "blushcreme_cream" : "Soft and creamy. Brings a vibrant, dewy fresh, slightly shiny look to the cheeks. Blends well, layers exquisitely. ",
   "help_or_order" : "If you need help or would like to order by phone, call customer service at 1.800.588.0700.",
   "alt.pnav_my_messages_off.gif" : "My Messages",
   "packaging_3" : "Packaging No.3",
   "alt.h_top_searches.gif" : "Top Searches",
   "chat_live_artist_note" : "If you wish for an immediate response and it is between 10 am and 10 pm EST, Live Chat with a MAC Cosmetics Online Makeup Artist.",
   "email_an_artist_form_note" : "The M·A·C attitude is expressed by our artists, seen and felt in our stores and accessed online. Email us to get answers to your questions.",
   "dark" : "Dark",
   "macpro" : "M·A·C Pro",
   "fair" : "Fair",
   "lipstick_lustre" : "Demi-sheer with wet-look lustre finish. Very slick. Makes lips look soft, smooth, ultra-moist.",
   "full_pan" : "Your palette is full. Remove a color by dragging swatch off palette or clicking an active swatch below.",
   "submit" : "Submit",
   "alt.pnav_nth_america_off.gif" : "NORTH AMERICA",
   "pro_acct_renewal_transaction" : "Transaction date",
   "item_in_favourites" : "item in favourites",
   "search_results" : "Your search for \"QUERY\" returned RESULTS <span>result(s)</span>.",
   "alt.fnav_live_chat_off.gif" : "Live Chat",
   "11_returns" : "Returns and Exchanges: Damaged Items",
   "alt.gnav_newsworthy_off.gif" : "Newsworthy",
   "somkey_eyes" : "Smokey Eyes",
   "register_pro_member_id" : "11 digit Pro Membership ID (12#########)",
   "member_info" : "Membership Information",
   "dropdown_another_issue" : "Another issue",
   "alt.shipping_address_off.gif" : "Shipping Address",
   "gc_redeeming_gc_by_phone" : "Redeeming M·A·C Gift Cards at  1-800-588-0070",
   "btm_co" : "c/o USA Fulfillment",
   "unsubscribe_unsubscribe" : "Unsubscribe email from M·A·C Cosmetics Online.",
   "makeup_services_fla" : "A demonstration to custom fit your new MAC Lashes.  Includes style consultation/selection, fitting, trimming and positioning  of the lashes to naturally enhance or add drama. Available in select  stores only.",
   "deep_golden_brown" : "Deep Golden Brown",
   "btm_return_packaging15" : "Empty Foundation Bottle Jar",
   "eye_shadow_veluxe_pearl" : "Vibrantly-toned. Velvety soft. Shimmeringly metallic. Veluxe eye shadow, now overlaid with high-shine pearl. ",
   "alt.h_account_profile.gif" : "Account Profile",
   "submit_es" : "Enviar",
   "country_de" : "Germany",
   "country_rme" : "Middle East",
   "makeup_services_gog" : "In this demonstration, glamour takes centre stage  as a MAC Artist builds on your existing makeup to show the classic and  up-to-the-minute product techniques of today's most dazzling looks.",
   "alt.btn_save_to_fav.gif" : "Save to Favourites",
   "dry" : " Dry",
   "gc_how_delivered" : "How will it be delivered?",
   "alt.show_all_finishes.gif" : "Show all finishes",
   "pnav_disc_prod_copy" : "Your one-stop resource to locate seasonal limited editions or those faves that went farewell. Check its in-stock status. See what our artists recommend in its place.",
   "gc_terms_conditions" : "Terms and Conditions",
   "btm_return_packaging5" : "Empty Paint Tube",
   "btm_return_packaging10" : "Empty Blush compact",
   "alt.pnav_detailed_search_off.gif" : "Detailed Search",
   "to_shop" : "To Shop",
   "alt.send_your_package_off.gif" : "Send Your Package",
   "alt.h_location_time.gif" : "Location & Time",
   "view_all_results" : "View all... Results",
   "n_chars_remaining" : "characthers remaining",
   "alt.gnav_shop_products_off.gif" : "PRODUCTS",
   "alt.btn_add_to_bag_182.gif" : "Add to Bag",
   "please_enter_valid_email_es" : "Ingrese una dirección de email válida",
   "register_pro_read_faq" : "Read about M·A·C Pro Membership.",
   "alt.h_mail_your_packaging.gif" : "Mail Your Packaging and Form to M·A·C",
   "alt.h_my_messages.gif" : "My Messages",
   "country_be" : "Belgium",
   "alt.h_subject.gif" : "subject",
   "powder_blush_sheertone" : "Shy-on tints in a sheer-on micro-refined powder. Not transparent. Goes on faint. Looks real.",
   "buy_now" : "Buy Now",
   "alt.pnav_australia_on.gif" : "Australia (shop online)",
   "lipstick_satin" : "Colour-rich. Soft satin, semi-matte finish. Conditions while adding intense colour.",
   "alt.h_about_mac_gc.gif" : "About M·A·C Gift Cards",
   "alt.h_on_the_art_of_makeup.gif" : "On The Art Of Makeup",
   "alt.i_will_not_attend.gif" : "I will not attend",
   "select_shade" : "Select Shade by Name",
   "use_mac_products" : "YES, I USE M·A·C PRODUCTS",
   "btm_return_packaging7" : "Empty Mascara Tube",
   "dont_use_mac_products" : "NO, I DON'T USE M·A·C PRODUCTS",
   "black" : "Black",
   "viva_glam_espanol_info" : "Sign me up to be informed with MAC Espanol website is live",
   "member_address" : "Member Address",
   "alt.pnav_email_artist_200x12_off.gif" : "Email an Artist Form",
   "alt.h_my_fav_looks.gif" : "My Favorite Looks",
   "alt.pnav_contact_info_off.gif" : "Contact Information",
   "alt.free_product_off.gif" : "Free Product",
   "lipstick_glaze" : "Low-colour impact with sheen-style finish. Glazes the lips, makes them shine!",
   "pro_acct_sitechange_intl" : "As a registered Pro Member, you will have access to view all M·A·C Products, including Pro Products.  <br><br>To access Pro exclusive content including artistry, trends, Pro events and Pro Membership details, <a href=\"macpro/index.html\" target=\"_blank\">visit macpro.com</a>.",
   "eye_color" : "Eye Colour",
   "lipstick" : "Lipstick",
   "7_order_status" : "Order Status",
   "email_answer_note" : "Please note that unfortunately the Senior Artists on the M·A·C Pro Team are unable to personally answer questions from the site. Your inquiry will be answered by a M·A·C Artist. All our Online Artists are highly skilled M·A·C trained make-up artists.",
   "alt.signup_image.jpg" : "M·A·C COSMETICS",
   "alt.h_locate_other_events.gif" : "Locate other events",
   "alt.h_date.gif" : "Date",
   "select_topic" : "For answers to common questions we receive, select a topic below.",
   "macpro_service_intro" : "For inquiries about the M·A·C Pro Membership program,   please select a topic below.",
   "mac_security_msg" : "At M·A·C Cosmetics Online we understand how important security is to you. When you place an order at M·A·C Cosmetics Online we encode your information using Secure Socket Layer (SSL) encryption technology. This is the most advanced consumer online security technique to date. You can be assured that your order will be placed safely and securely. We have used government approved encryption software. In order for encryption to take place, your browser must have the SSL protocol. The browsers that have this software are the following:",
   "signed_in_as" : "Signed in as",
   "alt.fnav_viewprofile_off.gif" : "VIEW YOUR PROFILE",
   "lipstick_matte" : "Pigment rich with intense colour pay-off. No-shine, all matte finish.",
   "dropdown_product_no_longer_available" : "Can I obtain a product that is no longer available?",
   "back_to_mac_copy" : "Because we share your commitment to the environment, M·A·C accepts returns of its primary packaging through the Back to M·A·C Program. By returning six [6] M·A·C primary packaging containers to a M·A·C counter or M·A·C Cosmetics online, you’ll receive a free M·A·C lipstick of your choice as our thanks to you.",
   "prolanding_benefits" : "<b>Product Discount</b><br> Present your Membership Card at M·A·C stores and select partnered stores around the world  to   receive a product discount on M·A·C Products, including Pro Products. You may also use your   discount online at www.M·A·Ccosmetics.com. International discounts may vary. Whether you’re on   a shoot or preparing for the stage, M·A·C PRO helps to keep your kit stocked with the    must-have shades, formulas and tools for any industry.<br><br> <b>Pro Only Events</b><br> Master Classes: Integral to the philosophy of the M·A·C culture, inspirational Master Classes   foster the growth of the artist community. Refine and build skills. Meet face-to-face with   celebrated inudstry pro’s. Watch makeup demonstrations to gain insights into application and   technique-theory.<br><br> Pro to Pro: Dialogue with M·A·C PRO Senior Artists in an atmosphere both informative and   informal.  Enjoy cocktails and hors d’oeuvres, mingle with makeup artists, hair stylists, and   connect with agents, models and performers that make your community tick. The ultimate   networking occasion for members to get the buzz and share ideas.<br><br> <b>Industry Privileges</b><br> M·A·C PRO Membership affords you special pro-only privileges. Special offers and discounts on   subscriptions, classes and more.<br><br> <b>M·A·C Pro Online</b><br> Stay informed with everything that’s new and now! Log into M·A·C PRO to enjoy all of your   online benefits including:<br> Pro only event schedules, Trend reports, Member Voice community and networking, Online   Membership Renewal, Artist Tips and Articles, Email An Artist, and the best in Pro Product   information.<br><br> <a href=\"macpro/index.html\"><font color=\"#FFFFFF\">Visit M·A·C PRO</font></a> to learn more about the benefits of membership.",
   "alt.1_will_attend_with_guest.gif" : "I will attend with a guest",
   "add_renewal" : "Add renewal to cart",
   "alt.cheeks.gif" : "Cheeks",
   "immed_response_live_chat" : "If you prefer an immediate response and it is between 10:00 am and 10:00 pm EST, Live Chat with a M·A·C Cosmetics Online Makeup Artist.",
   "golden_beige" : "Golden Beige",
   "alt.h_artists.gif" : "ARTISTS",
   "alt.pnav_eurpoe_off.gif" : "EUROPE",
   "hazel" : "Hazel",
   "alt.h_cheek_products.gif" : "Cheek products",
   "search_shades_results" : "Your search returned RESULTS result(s).",
   "special_occasion" : "Special Occasion (prom, wedding)",
   "pro_acct_renewal_no" : "Do not auto-renew my membership",
   "makeup_services_eyes" : "A MAC Artist demonstrates the tools, technicques  and products needed to create the Classic Neutral Eye, a Smoky Eye or  the ultimate fashion-forward Trend Eye",
   "in_us_mail_to" : "In the U.S. Mail to:",
   "eye_shadow_lustre" : "Smoothly pearlized; intensely frosted with deluxe ultra-fine \"conditioned\" finish.",
   "alt.gnav_giving_back_off.gif" : "GIVING BACK",
   "goinggone" : "Your last chance for limited-edition shades and products that are about to be gone.",
   "country_uk" : "United Kingdom",
   "prolanding_membership_program" : "<font color=\"#FFFFFF\">Welcome to M·A·C Pro! Since your last visit, we've made some changes to   the site. Sign in at MAC Pro Online to get Pro-exclusive content. Now shop all products using your Pro discount at maccosmetics.com.</font><br><br>  M·A·C PRO Membership is the industry's leading preferred customer program for the makeup   professional (Makeup Artists, Aestheticians, Cosmetologists, Hairstylists, Fashion Stylists,   Nail Technicians, Costume Designers, Models, On-Air Talent/Performers, and Photographers).   This paid-for membership program offers the makeup professional a direct link to the M·A·C   world as well as member-exclusive benefits. Throughout the program you can count on us to   provide you with the highest level of service, unique educational experiences and the most   comprehensive selection of professional makeup.<br><br> For anyone attending an eligible school or institution of Makeup Artistry or the Performing   Arts, we also offer the M·A·C PRO Student Program. It's the beginning of M·A·C's dynamic   relationship with any makeup professional just starting out. <br><br> <b>How do I become a member?</b><br> For information about program eligibility and details about membership benefits please visit   <a href=\"macpro/index.html\"><font color=\"#FFFFFF\">M·A·C PRO</font></a> or contact 1-877-553-5536   (available Monday through Friday from 9:00am to 5:00pm) or email <a   href=\"mailto:promembership@maccosmetics.com\"><font color=\"#FFFFFF\">M·A·C PRO   Membership</font></a>.",
   "combination" : " Combination",
   "alt.print_page.gif" : "Print page",
   "country_es" : "Spain",
   "alt.h_mixable_replacement_products_430x13.gif" : "Mixable Replacement Products",
   "prolanding_faq" : "Whether you’re interested in becoming a M·A·C PRO Member or you’re a Member and you still have   a few unanswered questions, here are some of those answers for you. Below are some of the most   frequently asked questions that we receive. Still have more questions? <a   href=\"customer_service/contact_us.tmpl\"><font color=\"#FFFFFF\">Contact Us</font></a>.<br><br>  <b>Where can I use my discount?</b><br> M·A·C PRO, M·A·C PRO Student and M·A·C PRO Preferred members can redeem their discount in   select partnered stores, at any freestanding M·A·C Store or M·A·C PRO Store around the world.    Discounts may vary from country to country.<br><br>  Members in Canada and the United States can use the discount benefit online at   www.maccosmetics.com, at any M·A·C Store, PRO Store or by telephone through our Customer   Service Department at 800.387.6707, ext. 8555.Unfortunately, at this time members outside of   Canada and the United States are unable to purchase product online at   www.maccosmetics.com.<br><br>  To locate a store location that accepts the M·A·C PRO discount closest to you please <a   href=\"locator/index.tmpl\"><font color=\"#FFFFFF\">click here</font></a>.<br><br>      If you live outside of the United States and Canada and cannot find a store in your area,   please contact our UK Mail Order Service via phone  01144 870 034 2627 to determine if   delivery options to your area are available.<br><br>  <b>How do I become a member, what discount will I get and am I eligible for the   program?</b><br> For M·A·C Pro Members in the United States or Canada, <a   href=\"macpro/cms/membership/howtoapply.tmpl\" target=\"_blank\"><font color=\"#FFFFFF\">View   Enrollment for details</font></a>.<br><br>  To apply for the M·A·C PRO Membership outside of Canada and the United States, contact your   local M·A·C Store for further information on the program and eligibility requirements in your   country.  To determine if there is a store near you, <a href=\"locator/index.tmpl\"><font   color=\"#FFFFFF\">click here</font></a>.<br><br>  For applicants in Australia, France, UK and Spain: applications are available for download,   please <a href=\"macpro/cms/membership/howtoapply.tmpl\" target=\"_blank\"><font   color=\"#FFFFFF\">click here</font></a>.<br><br>  <b>I want to train as a M·A·C makeup artist. Can you recommend a place where I can train? Is   there a course run by M·A·C?</b><br> At this time, M·A·C does not offer Make Up artistry certification and does not operate a school or   offer courses.  We have the M·A·C PRO Student Program for individuals who attend a school or   institute of learning which has undergone an approval process by our Artist Relations   Department. To see a full list of our eligible schools please <a   href=\"macpro/cms/membership/prostudent.tmpl\" target=\"_blank\"><font color=\"#FFFFFF\">click   here</font></a>. <br><br>  As a benefit of M·A·C PRO and M·A·C PRO Student Membership we offer Master Classes (generally   2-hour seminars on a specific aspect of Make Up) in M·A·C and M·A·C PRO stores. <br><br>  <b>Does being a part of M·A·C PRO help me qualify for a position to work at a M·A·C store or   counter?</b><br> Thank you for your interest in M·A·C PRO. We are always in search of bright, energetic   individuals to represent us. <br><br>  While M·A·C does offer advanced classes for professional artists in select cities in North   America, we do not have anything of this sort for aspiring artists just starting out.    Membership in M·A·C PRO is not qualification for a position at a M·A·C store or   counter.<br><br>  For a Makeup Artist position within a Department or Specialty store that carries M·A·C PRO,   please inquire with the Human Resources Director of the store and indicate your preference to   work at the M·A·C counter. <br><br>  For a Makeup Artist position in a M·A·C store, please inquire with the Store Manager directly.   To find address and phone number information for the counters in your area, go to <a   href=\"locator/index.tmpl\"><font color=\"#FFFFFF\">Find Stores</font></a>.<br><br>  <b>I want to sell M·A·C products. What should I do?</b><br> While we appreciate your interest in selling M·A·C products, our products are distributed for   sale only at our authorized retail store accounts, the majority of which are located in major   department stores or international perfumeries. While there are other points of sale, we are   not seeking to expand our distribution network at this time and we do not offer   franchises.<br><br>  <b>Where can I buy PRO product?</b><br> Members in Canada and the United States can use their discount benefit to purchase PRO product   online at www.maccosmetics.com, PRO Store or by telephone through our Customer Service   Department at 800.387.6707, ext. 8555. <br><br>   <b>How long does it take for an application to be processed and how long would it take to be   notified if I was approved or not? </b><br> If you submitted your application in-store please allow for 1-3 weeks before your membership   is processed.  If you sent your application directly to us, please allow 1 week before your   membership is processed.  Upon your application's arrival, your membership will be reviewed   that day.  If your membership is approved, please allow another 1-2 weeks for the delivery of   your card.  If your membership is declined, we will promptly send you a letter with an   explanation.<br><br>  Please remember that your card will be sent to the address that you submitted in your   application.  If you have recently changed address and need a replacement card or wish to   speak with a representative, please contact our processing department directly at 877.553.5536   or 905.940.2437.<br><br>  I am a M·A·C PRO Student member who has graduated. How can I obtain a PRO membership? Congratulations on graduating. If you are now working in the industry, we invite you to join   our M·A·C PRO Membership program and receive even more benefits. Please <a   href=\"macpro/cms/membership/howtoapply.tmpl\" target=\"_blank\"><font color=\"#FFFFFF\">visit our   How To Apply page</font></a> for details and to obtain an application.<br><br>  <b>How do I apply for the PRO Student Program? </b><br> The M·A·C PRO Student program is open to individuals enrolled in a school or institution of   Makeup Artistry, Cosmetology, Hairstyling, Aesthetics, Photography, Fashion Styling or the   Performing Arts that is on our list of eligible schools. For full details on the Pro Student   Program, Eligible Schools and how to apply, <a href=\"macpro/cms/membership/prostudent.tmpl\"   target=\"_blank\"><font color=\"#FFFFFF\">click here</font></a>.<br><br>  <b>How do I change my address and contact information? </b><br> Please log in and go to the MY ACCOUNT section, you can change your address, contact   information and email. If you are having difficulty changing your address please email <a   href=\"mailto: macprotech@macpro.com\"><font   color=\"#FFFFFF\">macprotech@macpro.com</font></a>.<br><br>  If you changed your name and require a new card please call our processing department directly   at 877.553.5536 or email your request to <a href=\"mailto:   promembership@maccosmetics.com\"><font   color=\"#FFFFFF\">promembership@maccosmetics.com</font></a>. <br><br>  <b>What do I do if I have lost my card?</b><br> If you changed your name and require a new card please call our processing department directly   at 877.553.5536 or email your request to <a href=\"mailto:   promembership@maccosmetics.com\"><font   color=\"#FFFFFF\">promembership@maccosmetics.com</font></a>.",
   "footer_note_2" : "so we can correct this matter. We apologize for any inconvenience.",
   "not_registered_user" : "NO, I'M NOT A REGISTERED M·A·C USER",
   "step" : "Step",
   "email_confrim_now_that_your" : "Now that you have signed up for email, why not create an account with us? When you have an account with M·A·C Cosmetics Online, you will enjoy many enhanced shopping services.",
   "alt.h_most_important_tool.gif" : "Most Important Tool",
   "alt.h_fav_mac_consumer_prod.gif" : "Favourite M·A·C Product (Consumer)",
   "alt.pnav_canada_on.gif" : "Canada (shop online)",
   "makeup_services_fs" : "A MAC Artist demonstrates how contouring and  high-lighting can create a dimensional and well-defined look by building  on your existing makeup.",
   "lipstick_sheer" : "Colour-light with sheer, near-translucent, naturally-tinted finish.",
   "description_CAT1063" : "In London, anything goes! East London, where I live, is a great melting pot of different people and fashions, and they all influence each other! Club kids, celebrities, media and fashion people, and there’s also the influence of Afro-Caribbean culture. It’s amazing to learn that Girl About Town and St. Germain Lipsticks are wildly popular around here – with me, too! My personal style has always been punk, a softly glamorous punk look…I am forever a fan of Liquidlast Liner in Point Black, Lip Pencil in Cherry, Ruby Woo Lipstick and Red Lipmix. The starting point for any beautiful makeup is gorgeous skin that looks naturally perfect. My faves are Face and Body Foundation, Select Moisturecover and Studio Finish Skin Corrector. And Lashes make eyes look bigger and more powerful. The bestseller worldwide is 7 Lash so you know they’re a sure winner, but 4 Lash is my favourite because they look fluffy and real!",
   "alt.return_packaging_off.gif" : "Return Packaging",
   "enter_location_event_search" : "TBD",
   "register_country_error" : "Please enter your country matching your membership information.",
   "found_locations" : "Found Locations",
   "alt.btn_add_to_bag_93.gif" : "Add to Bag",
   "btm_return_packaging1" : "Empty Eye Shadow Pan",
   "blushcreme_pearl" : "Soft and creamy, with a sophisticated pearl finish. Brings a vibrant, lightly reflective shimmer to the cheeks. Blends well, layers to the desired finish.",
   "no_search_word" : "Please enter a keyword to search.",
   "footer_copyright" : "© Make-up Art Cosmetics. All Worldwide Rights Reserved.",
   "alt.fnav_phone_num.gif" : "1-800-588-0070",
   "alt.h_country_chooser.gif" : "Country Chooser",
   "unsubscribe_contact_cs" : "Contact a customer service representative.",
   "alt.h_discontimued_prods_250.gif" : "Discontinued Products",
   "alt.lips.gif" : "Lips",
   "1_find_products" : "Find MAC Products",
   "contactus_email_subject_usca" : "Thank you for your email",
   "alt.h_events_near_you.gif" : "Events Near you",
   "alt.h_most_respected_designers.gif" : "Most Respected Designers",
   "pro_acct_renewal_note" : "PLEASE NOTE: You must sign up for auto-renewal prior   to the month in which your membership expires.",
   "alt.pnav_me_on.gif" : "Middle East",
   "shade_product_name" : "Shade/Product Name",
   "unsubscribe_valid_member" : "As a valued member of the M·A·C community, your privacy is important to us. We will never provide your personally identifiable information to third parties for their use in marketing their products or services to you without your consent. Review our privacy policy.",
   "eye_shadow_frost" : "An iridescent shine that adds a highlight to any colour. ",
   "pro_renewal_intro" : "Choose to renew your membership before it expires. You will continue through our easy checkout process. Once your payment is received, a new card will be sent to the address saved in your Member Profile. If your membership expires, you may only renew via phone or mail.*  Your membership fee is $35US/$45CDN. Your M·A·C PRO Membership lasts for one year.",
   "dropdown_product_concerns" : "Concerns about a M·A·C Product (address required)",
   "private_function_note" : "If you are interested in having a M·A·C Artist attend a private function, please contact a M·A·C store in your area.",
   "country_sg" : "Singapore",
   "6_disc_products" : "Discontinued Products",
   "alt.btn_email_directions.gif" : "Email Directions",
   "alt.h_my_favorites" : "My Favorites",
   "alt.print_your_form.gif" : "print your form",
   "comment" : "My Comments",
   "goodbyes" : "Goodbyes is the perfect last-chance resource to find end-of-line shades and products. Here are two online tools to source your most-wanted:",
   "signin_pro_clicktoregister" : "click here to register.",
   "makeup_services_jol" : "A personalized product demonstration focusing on  one key feature of your face",
   "read_more" : "Read More",
   "results" : "results",
   "thanks_es" : "¡Gracias!",
   "shoptogether_landingpage_meta_description" : "Join me for some virtual shopping. M∙A∙C Shop Together is just like being in a store together. We can instant message & see what we’re browsing in real time. Ready? Let’s go!",
   "radiant_or_dewey_finish" : "Radiant or Dewy Finish",
   "normal" : " Normal",
   "jump_to_date" : "Jump to Specific Date",
   "country_hk" : "Hong Kong",
   "alt.h_start.gif" : "Start",
   "alt.sales_tax.gif" : "Sales Tax",
   "btm_return_packaging14" : "Empty Foundation Tube",
   "btm_return_packaging6" : "Empty Pigment",
   "alt.pnav_mac_aids_fund_off.gif" : "MACAIDSFUND.ORG",
   "alt.pnav_terms_conditions_off.gif" : "Terms & Conditions",
   "gc_returns_exchanges" : "Returns and Exchanges",
   "alt.pnav_my_favorites_off.gif" : "My Favorites",
   "country_ca" : "Canada",
   "email_intro" : "Please use this form below to get in touch with us. Due to the volume of email we receive, it may take us a few days to respond. Please be sure to give us your full email address so we may respond to your inquiry. Between the hours of 10:00 am and 10:00 pm EST, Live Chat with a M·A·C Makeup Artist Online. We do appreciate the fact you've taken the time to let us know what you think, and we will do our best to incorporate appropriate suggestions into the site.",
   "alt.btn_back_to_trends.gif" : "Back to Trends",
   "alt.btn_play_colour.gif" : "Colour Play",
   "btm_return_packaging8" : "Empty Liquid Liner Tube",
   "alt.h_fav_tool_technique.gif" : "Favourite Technique",
   "btm_street" : "313 Talbot Blvd.",
   "alt.h_artist_tips.gif" : "Artist Tips",
   "search_shades_no_results" : "Your search returned 0 results.",
   "register_zip_error" : "Please enter the zip/postal code matching your membership information.",
   "makeup_services_learn" : "See the difference MAC can make as our Artists  reveal their tips, secrets and techniques. In-store product  demonstrations are available on a walk-in basis any day. What you take  away is always what you learn - our Pro know-how. Complimentary. Based  on Artist availability.",
   "alt.pnav_product_info_off.gif" : "Product Information",
   "alt.pnav_back_To_mac_off.gif" : "BACK TO M·A·C",
   "country_fr" : "France",
   "packaging_1" : "Packaging No.1",
   "limited" : "Limited Edition",
   "first_choice" : "First Choice",
   "alt.signup_header.gif" : "Thank you for signing up for email from M·A·C COSMETICS",
   "preferred_look" : "Preferred Look",
   "alt.h_questions_concerns.gif" : "Questions or Concerns",
   "makeup_services_unavail" : "Alabama, Minnesota, DC, Idaho, Oklahoma,  Maine, Montana.",
   "alt.h_most_afmired_artists.gif" : "Most Admired Makeup Artists",
   "country_ru" : "Russia",
   "meta_desc_homepage" : "Professional makeup artist quality cosmetics. Offering more than 100 shades for eyes, lips and face…everything a makeup addict can’t live without.",
   "pro_acct_renewal_yes" : "Set my membership to automatically renew each year.",
   "country_kr" : "Korea",
   "email_confirm_subject" : "MAC Cosmetics Online Email Signup Confirmation",
   "country_au" : "Australia",
   "gc_where_purchased" : "Where can it be purchased?",
   "alt.btn_return_to_map.gif" : "Return to map",
   "email_confrim_regards" : "Best Regards",
   "alt.alternate_image.gif" : "Alternate Image",
   "dropdown_order" : "My M·A·C Order",
   "contactus_email_subject_pro" : "Thank you for your comments",
   "eye_shadow_matte2" : "Intense, opaque matte finish with exceptionally rich colour payoff.",
   "btm_return_packaging2" : "Empty Trio Compact",
   "viva_glam_espanol_info_es" : "¡Inscríbete y te informaremos cuando salga el sitio web de M·A·C en español!",
   "sign_up_for_email" : "Email Me",
   "tracking_note" : "(multiple tracking numbers per order indicate separate shipping",
   "search_shades" : "Search shades",
   "events" : "events",
   "description_cat1055" : "Tokyo is the city where everything is stuffed to the max, styles clash and most importantly, it’s a place where you're completely free to display your own individuality. “Kawaii” makeup is hot right now. More natural than it has been last season. Sheertone Blush <span class=\"CAT1055PROD329SKU2967 shoppable\">Peaches</span> and <span class=\"CAT1055PROD329SKU2965 shoppable\">Pink Swoon</span> are IT! Dazzleglass <span class=\"CAT1055PROD1765SKU10784 shoppable\">Smile</span> is the must-have product for the new shiny looks. <span class=\"CAT1055PROD363SKU1662 shoppable\">Orb</span> Eye Shadow is particularly necessary for Asian models. Suddenly, instant brightness! And I can't leave this out: Fluidline is almighty. It can be used as eyebrow enhancer, eyeliner and eye shadow! That’s pretty incredible. The MUST to be added this year to every stylish Tokyo girl’s kit is <span class=\"CAT1055PROD970SKU4164 shoppable\">Prep + Prime Lash</span>.  Actually, it’s for eyelashes, but use it for eyebrows! All tidy in seconds! To go from day to night – and nightlife is quite spectacular and sometimes carnivalesque in Tokyo – add <span class=\"CAT1055PROD505SKU16378 shoppable\">Reflects Pearl</span> Glitter. Believe me, it gets everyone’s attention. And of course it’s mandatory to start with the perfect blank canvas: Studio Sculpt SPF 15 Foundation. Models very often have troubled skin. It’s saved me over and over!",
   "email_address_es" : "Dirección de email",
   "isp_msg_pro" : "PLEASE NOTE: Your Internet Service Provider (ISP) or email client may be using filters to block email that may affect the receipt of email from maccosmetics.com. 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. This will ensure that correspondence from maccosmetics.com will be accepted. Additional Information is available in our FAQ section above. If you have further questions, please contact us at 1-800-866-6464.",
   "call_to_place_order" : "1. Call 1-800-588-0070 to place your order.",
   "medium_to_dark" : "Medium to Dark",
   "deep_brown_pink" : "Deep Brown Pink",
   "register_pro_intro" : "If you already have a M·A·C Pro member number, you can use the form below to complete your online registration.",
   "alt.btn_submit.gif" : "Submit",
   "next" : "Next",
   "pro_renewal_footnote" : "*Please note: If your membership has lapsed for twelve months or more, you must resubmit current professional documentation with your M·A·C PRO Membership application by mail.",
   "footer_note_1" : "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",
   "back_to_mac_address" : "Back to M·A·C",
   "brand_member" : "Are you a brand member?",
   "natural" : "Natural",
   "unsubscribe_communication" : "Communication from M·A·C Cosmetics Online keeps you tuned in to all things M·A·C. You hear from us about products, services, events and special insider only offers.",
   "show_limited_edition" : "Show only Limited Edition",
   "alt.terms_conditions.gif" : "Terms and Conditions",
   "don’t_know" : " Don’t Know",
   "alt.pnav_consumer_awareness_off.gif" : "Consumer Awareness",
   "packaging_5" : "Packaging No.5",
   "email_confrim_view" : "View your Order Status and track your shipments.",
   "phone_number" : "Phone Number",
   "alt.btn_chat_live.gif" : "Chat Live with our Makeup Artists - Chat Live",
   "signin_pro_registration" : "If you are a M·A·C PRO member who has previously registered on our site, please sign in under \"Already Registered\" with your email and password. If you are a member and would like to register to receive your special PRO privileges,",
   "pro_no_renewal_history" : "You have no past renewal charges on record.",
   "alt.returns_exchanges.gif" : "Returns and Exchanges",
   "brand_user" : "Are you a brand user?",
   "gc_redeeming_gc_online" : "Redeeming M·A·C Gift Cards at M·A·C Online",
   "11_order_cancellation" : "Order Cancellation",
   "2_careers_corporate" : "Careers with MAC Corporate",
   "grey" : "Grey",
   "will_do_best_to_fulfill_note" : "We will do our best to fulfill your preference in the order you have stated based on inventory availability",
   "pink" : "Pink",
   "powder_blush_sheertone_shimmer" : "Jet-milled and micro-fine, just like Sheertone, but tickled with just-enough shimmer to make light dance on the cheeks.",
   "dropdown_product_alt" : "Can you recommend a alternative for a discontinued product or shade?",
   "pro_discount" : "",
   "pro_acct_renewal_payment" : "When enrolled in this program please make sure that   your payment information is up to date.",
   "use_form" : "Please use this form below. Due to the volume of email we receive, it may take us a few days to respond.",
   "alt.h_prev_purchased.gif" : "Previously Purchased",
   "undertone_of_skin" : "Undertone of Skin",
   "makeup_services_fof" : "With a shade palette customized to your skin tone,  this demonstration builds on your existing makeup to show you how to use  product to enhance features.",
   "email_thanks" : "Thank you, your questions have been submitted.",
   "select" : "Select",
   "email_confrim_review" : "Review and shop from your Order History.",
   "alt.h_my_fav_tips.gif" : "My Favorite Tips",
   "alt.h_select_return_packaging" : "Select Your Return Packaging",
   "country_us" : "United States",
   "eye_shadow_matte" : "Hi-colour pay-off in a no-shine matte finish. ",
   "items_in_favourites" : "items in favourites",
   "alt.btn_compare_brushes.gif" : "Brush Finder",
   "btm_city_state_zip" : "Chestertown, MD 21620",
   "btm_return_packaging13" : "Empty Foundation Bottle",
   "chrs_remaining_plural" : "characters remaining",
   "country_in" : "India",
   "mac_me_over_campaign" : "Do you have style, heart and soul? Compete for a chance to become the face of M∙A∙C’s Fall 2011 campaign at <a href=\"http://www.macmeover.com\" target=\"_blank\">www.macmeover.com.</a>",
   "alt.eyes.gif" : "Eyes",
   "alt.h_new.gif" : "New",
   "register_pro_last_name" : "Your last name as it appears on member card",
   "alt.btn_print_look.gif" : "Print look Information",
   "alt.btn_get_directions.gif" : "Get Directions",
   "oily" : " Oily",
   "alt.fnav_choose_country_off.gif" : "Choose your Country",
   "country_nl" : "Netherlands",
   "alt.h_rsvp_event.gif" : "RSVP Event",
   "most_primary_packaging" : "* Most primary packaging of M·A·C products is accepted as Back to M·A·C \"currency,\" M·A·C does not accept the following as a part of the program:  secondary packaging of any sort (paper box, plastic/cellophane wrapper, shopping bags, etc), samples (sample containers, special/trial size products for promotional/seasonal kits), cosmetic applicators/tools",
   "green" : "Green",
   "artists_replacements" : "Our artists have recommended the following replacements for you.",
   "alt.h_lip_products.gif" : "Lip products",
   "alt.h_featured_products.gif" : "Featured Products",
   "nicki_minaj_info" : "Check it Out: Nicki Minaj's exclusive lipstick Pink Friday is only available for purchase Fridays! Sign up here to receive an alert when it is available while supplies last.",
   "gc_where_redeemed" : "Where can it be redeemed?",
   "alt.h_replacement_product_430x13.gif" : "Replacement Product",
   "brown" : "Brown",
   "please_describe_concern" : "Please describe your question or concern in as much detail as possible.",
   "alt.pnav_my_account_off.gif" : "My Account",
   "your_personal_message" : "Your personal message will be placed here.",
   "alt.fnav_sitemap_off.gif" : "Site Map",
   "register_birth_error" : "Please enter your birth month and day matching your membership information.",
   "membership_id" : "Membership ID:",
   "eye_shadow_velvet" : "Soft look finish with high-colour intensity. Has a plush velvety look and feel.",
   "artists_intro" : "They're our Artists backstage, on set, behind the scenes. Our all-star pros. Find out who they are. Where they're coming from. Latch onto their secrets. Lash onto a look. Brush up on techniques. <a href=\"http://www.macartiststweets.com/\" target=\"new\">Follow M·A·C Artists Tweets</a>.",
   "not_registered_user_pro" : "NO, I'M NOT A REGISTERED M•A•C PRO USER",
   "third_choice" : "Third Choice",
   "signup_thanks" : "Your address was added to our mailing list.",
   "alt.h_artists_tools_430.gif" : "Artist’s Tools",
   "alt.pnav_employment_off.gif" : "Employment",
   "mac_me_over" : "Enter M∙A∙C Me Over!",
   "eye_shadow_satin" : "Pure colour in a satin-style finish. Provides a soft subtle non-frost, light-refractive sheen.",
   "btm_return_packaging3" : "Empty Quad Compact",
   "contactus_email_subject_artist" : "Ask an Artist",
   "find_store" : "Find a store or event",
   "print_form_complete" : "your form is now complete. Please ensure to print your form and mail it to the address below.",
   "alt.how_delivered.gif" : "How will it be delivered?",
   "skin_type" : "Skin Type",
   "please_enter_valid_email" : "Please enter a valid email address",
   "packaging_2" : "Packaging No.2",
   "alt.collection_products.gif" : "Collection Products",
   "8_shipping_info" : "Shipping Information",
   "country_at" : "Austria",
   "gc_select_topic" : "For more information about M·A·C Gift Cards, select a topic from below.",
   "4_appointments" : "Bridal or Private Appointments",
   "country_cn" : "China",
   "description_CAT1093" : "Don’t neglect your skin! It all starts with three products I can’t live without. First, cleanse delicately with Lightful Foaming Crème Cleanser then treat with Care Blend Essential Oils. They leave the skin feeling incredibly supple without any tackiness. And my best secret for gorgeous skin: mix Lightful Charged Essence into your foundation for a mannequin-like finish. Another product I love is Fix+. It does everything! Blends and sheers an “over-concealed” eye, highlights skin, creates a rich finish when mixed with mineral eye shadows and blushes and sets makeup when you’re done! Of course, I’m all for colour, too. I love subversive, unexpected placement of shadows, like electric blues in the inner and outer corner of the eye and acid green in the middle, all applied organically, and topped with gloss for a watercolour feel! If you want to know my favourite product to give away (and sometimes it grows legs of its own), it’s Plushblack! Instant drama!",
   "gc_lost_stolen_gc" : "Lost or Stolen M·A·C Gift Cards",
   "alt.h_artistry_in_action_430.gif" : "Artistry in Action",
   "9_apo_fpo" : "APO / FPO, P.O. Boxes and International",
   "zip_code_colon" : "zip code:",
   "alt.h_email_an_artist.gif" : "EMAIL AN ARTIST",
   "alt.gnav_artistry_off.gif" : "ARTISTRY",
   "callout_viva_glam" : "Sign up to spread the word &amp; raise awareness about the impact of HIV/AIDS on women, men and children everywhere. Visit <a href=\"http://www.vivaglam.com\">www.vivaglam.com </a>, see Gaga’s VIVA GLAM video by Nick Knight &amp; join the conversation via Tweet #macvivaglam.",
   "alt.btn_find_events.gif" : "Find Events",
   "medium" : "Medium",
   "email_signup_text_es" : "Deseo inscribirme y recibir información de M·A·C Cosmetics sobre futuros productos, servicios, eventos y ofertas especiales sólo para miembros.",
   "alt.gnav_my_mac_off.gif" : "My M·A·C",
   "description_cat1090" : "I live in Orange County and have all of my life. I won’t be caught out and about in the O.C. without Studio Fix Powder Plus Foundation, Fix + , Studio Finish Concealer SPF 15 and <span class=\"CAT1090PROD305SKU2960 shoppable\">Show-off</span> Brow Set (I use it on my eyebrows AND beard)! I also want a totally tan look for the summer months so I use <span class=\"CAT1090PROD302SKU3989 shoppable\">Matte Bronze</span> Bronzing Powder with my trusty <span class=\"CAT1090PROD304SKU5401 shoppable\">129 Powder/Blush Brush</span>. I do enjoy the beach like most Cali natives, so I always protect my face with <span class=\"CAT1090PROD1544SKU4657 shoppable\">Prep + Prime Face Protect SPF 50</span>. It stays on ridiculously better than regular sun block! I live for our <span class=\"CAT1090PROD472SKU4920 shoppable\">Lip Conditioner</span> (the one that comes in a squeeze tube). My lips are NEVER dry. I can even skip a day! There is a huge scene in Orange County that is inspired by the 1950s-1960s. Flawless put-together makeup. Studio Stick Foundation SPF 15 with Mineralize Foundation/Loose SPF 15 on top. For eyes, they want a sharp, black Marilyn Monroe line and they’re crazy for new <span class=\"CAT1090PROD4253SKU10457 shoppable\">Penultimate Eye Liner</span>. These ladies look like Golden-Era Hollywood Starlets. I don’t think this look is going anywhere – a certain blonde from the O.C. who sings for a super-popular band put the look on the map globally and we are all very proud…I am at least! There are plenty of girls who keep it simple, fresh and what I like to call “Beach Beauty”. These girls love Face and Body Foundation or Studio Moisture Tint SPF 15. They prefer to intensify natural features so I always use <span class=\"CAT1090PROD7207SKU16434 shoppable\">Opulash Mascara</span> – my all-time favourite mascara! On the lips it’s usually a warmer nude shade like <span class=\"CAT1090PROD310SKU10675 shoppable\">Shy Girl</span>, <span class=\"CAT1090PROD310SKU2773 shoppable\">Hug Me</span>, <span class=\"CAT1090PROD310SKU3039 shoppable\">Cherish</span> or <span class=\"CAT1090PROD310SKU2788 shoppable\">Jubilee</span> Lipstick with <span class=\"CAT1090PROD309SKU2691 shoppable\">Viva Glam V</span> Lipglass – something every O.C. fashionista has.",
   "lipstick_frost" : "Excellent colour payoff with medium to high frosted shimmer-and-shine finish.",
   "country_jp" : "Japan",
   "alt.pnav_sth_america_off.gif" : "SOUTH AMERICA",
   "gc_sales_tax" : "Sales Tax",
   "dropdown_find_product" : "Where can I find M·A·C products near me (address required)",
   "email_confirm_success" : "You have successfully signed up for email from M·A·C Cosmetics Online. In the coming weeks, you will begin hearing about products, services, events and special insider only offers. We appreciate your interest in M·A·C Cosmetics and look forward to keeping you tuned in.",
   "alt.fnav_customer_service_off.gif" : "Customer Service",
   "alt.pnav_shopping_offline_off.gif" : "Shopping Online",
   "select_finish" : "Select a finish",
   "download_flash" : "Download Flash",
   "email_confrim_qualify" : "Qualify to redeem special offers such as Free Shipping.",
   "directions" : "Drag swatch off palette to remove",
   "powder_blush_satin" : "Light refractive sheen-style finish. Adds subtle highlights to the skin.",
   "country_chooser" : "Country Chooser",
   "gc_maximum_purchase_policy" : "Maximum purchase policy",
   "alt.limited_edition_packaging.gif" : "Limited Edition Packaging No Longer Available",
   "favourites" : "favourites",
   "found_events" : "Found Events",
   "alt.pnav_mac_stores_off.gif" : "M·A·C Stores",
   "pro_existing_acct_pw" : "Do you already have an existing maccosmetics.com account? If so, please enter the email address and password of that account below and we will merge your account information into your new M·A·C Pro account.",
   "yellow_golden_olive" : "Yellow-Golden Olive",
   "tartan_special_message" : "Tartan Tale Eye Shadows are now 25% off. Your M·A·C Pro discount does not apply online at this time.",
   "white" : "White",
   "very_fair" : "Very Fair",
   "alt.btn_print_directions.gif" : "Print Directions",
   "see_all_product_shades" : "See all ::product name:: shades",
   "eye_shadow_veluxe" : "Superfine pigment rich and very luxe. Supple to apply. Provides silky smooth matte finish. ",
   "alt.remove_off.gif" : "Remove",
   "packaging_6" : "Packaging No.6",
   "alt.h_attributes.gif" : "Attributes",
   "btm_return_packaging9" : "Empty Powder Compact",
   "gnav_checkout" : "Checkout",
   "unsubscribe_subject" : "unsubscribe subject line",
   "btm_name" : "Back to M&middot;A&middot;C",
   "skin_tone" : "Skin Tone",
   "gc_delivery_cost" : "How much does delivery cost?",
   "alt.btn_view_look.gif" : "VIEW LOOK",
   "dropdown_artist" : "Ask a M·A·C Makeup Artist",
   "alt.btn_add_look_to_bag.gif" : "Add look to bag",
   "second_choice" : "Second Choice",
   "makeup_services_intnote" : "Also pioloting internationally in Germany,  South Africa, Sweden, Denmark, Russia, Greece, (TBC), Hong Kong,  Thailand, Australia, Japan, Malyasia, New Zealand, Taiwan, China,  Indonesia, Philippines, Singapore, LATAM: Argentina, Brazil, Mexico,  Peru, Venezuela, Chile",
   "invalid_email" : "The email address entered is invalid.",
   "alt.collection_looks.gif" : "Collection Looks",
   "beige" : "Beige",
   "alt.i_will_attend.gif" : "I will attend by myself",
   "alt.h_yourcontactinfo.gif" : "Contact Information",
   "alt.pnav_search_discont_products_off.gif" : "Search Discontinued Products",
   "detailed_search" : "Detailed Search",
   "alt.pnav_privacy_policy_off.gif" : "Privacy Policy",
   "blue" : "Blue",
   "alt.h_fav_feature.gif" : "Favourite Feature",
   "pro_acct_renewal_reference" : "Reference #",
   "alt.pnav_ordering_offline_off.gif" : "Ordering Online",
   "lipstick_lip_treatment" : "Colour-free. Texture-free. Provides natural healthy-looking lips.",
   "goodbyes_spp" : "Your last chance to find that seasonal trend shade, fabulous limited-edition, or favourite products that are about to go on a permanent vacation. Quick, get them before they're gone!",
   "alt.h_contact_info.gif" : "Contact Information",
   "alt.fnav_terms_off.gif" : "Terms/Conditions",
   "alt.pnav_my_profile_off.gif" : "My Profile",
   "light_brown" : "Light Brown",
   "alt.free_shipping.gif" : "Free Shipping",
   "email_confrim_mac_acct_mgmt" : "M·A·C Cosmetics Online Account Management http://www.maccosmetics.com",
   "in_ca_mail_to" : "In Canada Mail to:",
   "btm_return_packaging12" : "Empty Lipglass Tube",
   "description_cat1089" : "New York is my town but I live in Brooklyn – the melting pot of bad and good makeup trends – past, present and future! In Manhattan loud lip colour is back! But I love the girls from eras past in Brooklyn who were trendy then and stayed there! They remind me of where makeup has been and what could be fabulously reinvented! Here, no matter who she is and what she likes – she is wearing <span class=\"CAT1089PROD7207SKU16434 shoppable\">Opulash</span> and <span class=\"CAT1089PROD4462SKU10795 shoppable\">Prep + Prime Transparent Finishing Powder</span>. I love <span class=\"CAT1089PROD305SKU2963 shoppable\">Girl Boy</span> BrowSet to add subtle highlights and <span class=\"CAT1089PROD263SKU1296 shoppable\">Stud</span> Brow Pencil to sketch in razor sharp volume. The four musts in my ultimate kit would be Fix + (to sheer out and blend things), Select Cover-up Concealer (because it is a durable matte finish), <span class=\"CAT1089PROD4467SKU10772 shoppable\">Skin Refined Zone Treatment</span> (I use this as a liquid powder – and it makes you look younger and tighter!) and Lipmix (it is so vibrant and the texture is just amazing!). Fall is eyeliner time. But be warned – Feline Kohl Power sells out fast! It gets \"permanently borrowed\" backstage all the time!! If black isn't your thing – <span class=\"CAT1089PROD323SKU1451 shoppable\">Powersurge</span> makes you look candlelit and soft.",
   "maximum_215_chars" : "(maximum 215 characters)",
   "contact_us_thank_you" : "We value your interest and look forward to serving you. If your question or concern is urgent, please call us at 1-800-588-0070 and we will be happy to assist you. <br> PLEASE NOTE: Your Internet Service Provider (ISP) or email client may be using filters to block email that may affect the receipt of email from maccosmetics.com. 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. This will ensure that correspondence from maccosmetics.com will be accepted. Additional Information is available in our FAQ section. If you have further questions, please contact us at 1-800-588-0070."
};
        
            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.",
   "delivery" : "",
   "unicode_script.first_name_alternate" : "Please enter First Name with katakana.",
   "session_timeout" : "You have been signed out due to inactivity. Please sign in again. ",
   "taken.mobile_email_address" : "The mobile email address you entered is already registered.",
   "required.ship_zip" : "Please enter a postal code.",
   "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.",
   "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 enter your phone number.",
   "required.pc_eyecolor" : "Please tell us the color of your eyes.",
   "offer_criteria_not_met" : "You did not meet the criteria for offer code ::offer_code::.",
   "unavailable.payment_type.cod.no_cod_to_postal_code" : "We're sorry, but COD is not available at this address.",
   "required.ship_city" : "Please enter the city/town for this address.",
   "no_to_address" : " No to address specified for emailer.",
   "format.password" : "Please enter 6-12 alpha-numeric characters.",
   "required_or.email_address.device_id" : "Please enter your email.",
   "required.pp_payment_type" : "Please, choose the payment method below.",
   "required.payment_type" : "Please select a payment method.",
   "error_find_page" : "To see if the page is still available or to find similar information, please try the following:",
   "cart.cost_limit" : "There is a maximum of US $500.00 per order. Please remove some items from your cart. For details, refer to our <a  href=\"cms/customer_service/shoponline.tmpl#max_purchase\"><font color=\"#333333\">Maximum Purchase Policy</font></a>.",
   "email_signup_deja_vu" : "To change your email address or unsubscribe to the email newsletter please visit the update page. ",
   "required.pc_sunresponse" : "Please tell us how your skin responds to the sun.",
   "no_addr_found" : "That address was not found in your address book. ",
   "other_payment_method" : "OTHER PAYMENT METHODS",
   "required_or.phone1.phone2" : "Please enter a phone number.",
   "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::).",
   "required.email_address_verify.waitlist_email_signup" : "Please re-enter your email address to verify it.",
   "required.question.macpro_contact_us" : "Please select Inquiry.",
   "obscene.custom_username.macpro_member_registration" : "Bad language filter. Please enter a new screenname.",
   "line_item_added" : " ::PRODUCTNAME:: in ::PROPERTY:: has been added to your shopping bag.",
   "session_pw_hint" : "Please enter your email address to receive your password hint.",
   "required.comments.macpro_artist_email" : "Please add a Comment for the MAC PRO Artist.",
   "ccnumber" : "Please supply your credit card number.",
   "required.retpack5.giving_back" : "Please select your fifth return product.",
   "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.",
   "invalid.pc_email_address" : "Please enter your email address in the following format: jane@aol.com",
   "required.email_address_verify.email_signup" : "Please reenter your email address for verification.",
   "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.user_choice" : "The Contest Entry Field is required.",
   "update_acct" : "Your account information is not yet complete. Please update below.",
   "required.retpack6.giving_back" : "Please select your sixth return product.",
   "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.zip" : "Please enter your zip 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" : "Your order cannot exceed a total of US $500.00 per order. You are also able to purchase 1 Basic Colour Kit or 1 Advanced Colour Kit or 1 Foundation Kit. Please remove some items from your cart. For details, refer to our <a  href=\"cms/customer_service/shoponline.tmpl#max_purchase\"><font color=\"#333333\">Maximum Purchase Policy</font></a>.",
   "required.ship_address" : "Please enter your address.",
   "required.offer_products_flag" : "The offer code ::variable:: is not valid.",
   "required_and.phone1_area_code.phone1_part1.phone1_part2" : "Please provide your cell phone number.",
   "identical.email_address.email_address_verify.identical.email_signup" : "Please check that the email address in the two fields are identical.",
   "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.",
   "unicode_script.last_name_alternate" : "Please enter Last Name with katakana.",
   "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.",
   "acct_notfound" : "Please review your information and try logging in again. If you continue to experience difficulty logging into the site, please contact us at <a href=\"mailto:promembership@maccosmetics.com\"><font color=\"#333333\">promembership@maccosmetics.com</font></a>.",
   "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.",
   "pro_renewal_payment.required" : "Please provide your default payment information to activate Pro auto renewal.",
   "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.",
   "cardnumber_type_mismatch" : "The Payment Type does not match your Credit Card Number. Please check your Payment Type.",
   "required.question.contact_us" : "Please select Inquiry.",
   "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.",
   "removed_item.pro_giftcard" : "Pro Members are not eligible to purchase Gift Cards online at this time. ::skuname:: has been removed from your cart. Find <a style=\"color:#777777\" href=\"locator/index.tmpl\">a M·A·C store</a> near you to purchase a Gift Card.",
   "required.name" : "Please enter your Kangi/Kana/ascii name.",
   "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.",
   "required.current_password" : "Please enter your current password.",
   "required.accept_terms" : "Please accept the Terms and Conditions in order to Create an Account.",
   "pro_restricted_access" : "You have attempted to access an area that is available only to M·A·C PRO Members as a benefit of membership. Please sign in below, or <a href=\"macpro/cms/membership/howtoapply.tmpl\">click here</a> to learn how to become a member. If you feel you are receiving this message in error, please contact us at <a href=\"mailto:promembership@maccosmetics.com\">promembership@maccosmetics.com</a>",
   "mpp_long_out_of_stock" : "This item is currently not available. You will be kept informed via email.",
   "required.birth_month.macpro_member_registration" : "Please select your birth month",
   "phone" : "",
   "required.security_question_id" : "Please select a secret question.",
   "pwhint_contains_pw" : "Please use a hint that does not contain your password.",
   "giftcard.zero_balance_checkout" : "There is no value left on your gift card. The balance of your order will be charged to your credit card.",
   "cardmessage_toolong" : "Please limit the length of the gift card message to 280 characters. ",
   "required.binary_file" : "A file is required.",
   "required.birth_day.macpro_member_registration" : "Please select your birth day",
   "required.pc_email_address" : "Please enter your email address.",
   "required.first_name_alternate" : "Please enter your First Name kana.",
   "distinct.pc_email_address.mobile_email_address" : "Your PC email address and your mobile email address must be distinct.",
   "shipping_upgrade_pomilcan" : "Air Force Post Office / Fleet Post Office, for an order addressed to PO Boxes, or 2 business day delivery service, or next-day delivery service is temporarily unavailable.",
   "required.comments.macpro_contact_us" : "Please enter a comment.",
   "required.comments.email_an_artist" : "Please provide additional information.",
   "giftcard.vl_connect_failure" : "We were unable to connect to the Gift Card processor. Please try again later or contact a customer service representative.",
   "required.pc_oiliness" : "Please tell us about the oiliness or dryness of your skin.",
   "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.",
   "identical.new_password.new_password_verify" : "The new passwords you have provided do not match. Please re-enter your new password.",
   "required.first_name" : "Please enter your 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 zip 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 bag.",
   "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.",
   "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.",
   "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.",
   "lipstick" : "Lipstick",
   "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.",
   "pro_discount_none" : "Pro discount does not apply.",
   "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.",
   "account_lockout" : "Your account has been locked. To cancel, please contact customer service 1-800-588-0070.",
   "required.address1" : "Please enter the street address",
   "cart.merge" : "Please review your order carefully as your shopping bag may contain some items from a previous visit.",
   "required.housing_number.macpro_contact_us" : "Please enter your house number.",
   "distinct.password.new_password" : "Your new password cannot be your temporary password. Please enter a different password.",
   "multiset_limit_exceeded" : "You've selected more than the allowed number of sample items. Please select ::variable (sample max):: samples.",
   "required.ship_last_name" : "Please supply a Kangi/Kana/ascii Last Name.",
   "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.",
   "required.shade1.giving_back" : "Please select your first shade preference.",
   "format.pro_id.macpro_contact_us" : "Please enter a valid Pro Membership ID in this format (12#########)",
   "cart.empty" : "Your shopping bag cart does not contain priced items. ",
   "email_address_verify" : " The Email Address fields must be identical.",
   "sku_unavailable" : ": ::variable::  is no longer available, and has been removed from your cart.",
   "required.card_number" : "Please enter your credit card number.",
   "csz_z" : "Your zip/postal code does not match your city and state/province.",
   "max" : "",
   "invalid_mime_error" : "That file type is not supported.",
   "required.valid_phone" : "Please enter your phone number.",
   "required.address" : "Please enter your address.",
   "limit.gc_balance" : "Please call 1.800.588.0700 for help with your gift card balance.",
   "required.password.password_reset" : "Please enter your temporary password.",
   "ca_addr_not_country" : "You entered a Canada address, but did not select Canada as your country.",
   "required.accept_privacy" : "Please accept the Privacy Policy in order to Create an Account.",
   "required.card_type" : "Please select a payment type.",
   "no_shipmethod" : "Please select a shipping method.",
   "length_min.new_password" : "Please supply a password that is at least 6 characters long.",
   "address_has_cc" : "You have at least one credit card associated with this address. Click here to make changes to your billing information",
   "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",
   "required.user_type.macpro_artist_email" : "Please select your User Type.",
   "offer_one_time_use_only_redeemed" : "This offer code has already been redeemed and is no longer valid.",
   "email_taken" : "Our records indicate that this email address already has an account associated with it. You may either use another email address or click here to go to the sign in page",
   "required.ship_prefecture" : "Please select the prefecture for this address.",
   "reset_password_not_signed_in_user" : "Please sign in to reset your password.",
   "required.member_number" : "Please enter a valid Membership Number.",
   "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" : "An account already exists with that email address. Please select a different email address.",
   "sku_unavailable_7" : "Unfortunately, ::variable::  is sold out, and has been removed from your cart.",
   "required.account_first_name2" : "Please enter your First Name kana.",
   "birth_month" : "Please provide your birth month.",
   "required phone" : "",
   "auto_fss_met" : "You have qualified for free standard shipping!",
   "invalid.password.checkout~checkout_signin" : "We do not recognize your sign in information. Please try again. Please note the password field is case sensitive.",
   "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 and state.",
   "required.ship_street" : "Please enter the street/block no.",
   "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.",
   "required_dependency.pc_email_promotions.email_address.required_dependency.waitlist_email_signup" : "Please complete the missing information.",
   "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.",
   "required_dependency.pc_email_promotions.pc_email_address.required_dependency.checkout_guestuser_contactinfo" : "Please provide your email address so we can send you a copy of your order information.",
   "illegal_from_address" : "Email cannot send from address.",
   "ship_city_error" : "Your Address city/town does not match your prefecture and postal code.",
   "required.shade3.giving_back" : "Please select your second shade preference.",
   "invalid.password.signin" : "We do not recognize your sign in information. Please try again. Please note the password field is case sensitive.",
   "required.account_last_name" : "Please enter your Last Name.",
   "min" : "",
   "account_update_signin" : "After logging on, please update your information.",
   "signedin.required" : "Please sign in below.",
   "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.",
   "incorrect_pwremind" : "We do not have a password associated with that email address. Please sign in as a new customer.",
   "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",
   "required.shade2.giving_back" : "Please select your third shade preference.",
   "addr_cc_deleted" : "Your credit card has been deleted.",
   "offer_ineligible" : "Sorry, you are not eligible to use offer code ::variable::. 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.",
   "required.house_number" : "The House Number field is required.",
   "pro_restricted" : "Only PRO members can signin to the MAC PRO site. If you wish to sign in to MAC Cosmetics, please click <a href=\"/account/signin.tmpl\">here</a>.",
   "required.ship_address_type" : "Please select the type of address.",
   "required.locator_country" : "Please select a country.",
   "required.account_last_name2" : "Please enter your Last Name kana.",
   "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 ::offer_code:: 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/FPO address.",
   "delivery_restriction.hazmat.item" : "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/FPO address.",
   "restricted.ship_method.hi" : "Second Day and Overnight Service are not available for orders going to Hawaii.",
   "required.password_hint" : "Please enter 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 a phone number that is 10-12 numbers and does not start with a 0 or 1.",
   "required.checkout_email_address_verify" : "The Email Addresses you have provided do not match. Please re-enter your email address.",
   "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 zip code for US addresses or in the following format for CA addresses: Z9Z 9Z9.",
   "migrated.mobile_account.signin" : "If you do not have a password (previous mobile users, etc), please create an account.",
   "required.pro_id.contact_us" : "Please enter your Pro membership ID.",
   "required.account_first_name" : "Please enter your First Name.",
   "required.locator_state_province" : "Please select a state/province.",
   "required.ship_first_name" : "Please supply a Kangi/Kana/ascii First Name.",
   "select_a_giftcard" : "Please select a Gift Card Value.",
   "account_prod_discontinued" : "This product has been discontinued and is no longer available online.  We recommend ::variable::.",
   "required.city" : "Please enter the city/town for this address.",
   "shipmethod_set_to_default" : "We apologize for any inconvenience. Currently only standard shipping is available for your order. We have changed this automatically for you. If you have any questions please contact us at 1-800-588-0070.",
   "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.",
   "required.membership_number.admin_po_validate_pro" : "The Pro member ID does not match what we have on record.",
   "account_postalcode_error" : "Your Address zip code does not match your city/town.",
   "please_signin" : "Please sign in below. ",
   "duplicate_username.custom_username.macpro_member_registration" : "That nickname is already in use. Please select another nickname.",
   "invalid.password2.registration" : "Please enter a password that is 6-12 characters.",
   "required.ship_first_name2" : "Please supply your Katagana First Name.",
   "required.locator_city" : "Please select a city.",
   "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.",
   "required.college_name" : "A College Name is required.",
   "required.pc_email_address2" : "Please re-enter your email address.",
   "identical.email_address.email_address_verify.identical.waitlist_email_signup" : "The Email Address fields must be identical.",
   "nonunique_shipoffer" : "Please note only one offer code can be accepted per order.",
   "address.not_found" : "We're sorry, but there was an issue with one of your addresses. Please contact customer service.",
   "email_signup_email_one_type_required" : "You must indicate at least one choice of email you would like to receive.",
   "distinct.new_password.password_hint" : "Your new password cannot be your password hint. Please try again.",
   "removed_item" : "::skuname:: has been removed from your cart.",
   "format.postal_code" : "",
   "gc_no_value" : "There is no value left on your gift card. The balance of your order will be charged to your credit card.",
   "required.phone1.giving_back" : "Please provide a phone number.",
   "invalid.password.macpro_signin" : "We do not recognize your sign in information. Please try again. Please note the password field is case sensitive.",
   "account_prefecture_error" : "Your State/Province does not match your city/town and postal code.",
   "required.account_zip" : "Please enter a zip code.",
   "email_only_acct_found" : " This feature is only provided to registered members.",
   "gift_card_value" : "Please select a gift card value.",
   "invalid.new_password.password.rest" : "Please supply a password that is at least 6 characters long.",
   "required_and.password.password2.password_hint" : "If you wish to create an account, please enter a password, verify password and password hint.",
   "required..zip_for_event" : "Please provide your zip code so we may notify you of [BRAND] events in your area.",
   ".membership_number.admin_po_validate_pro" : "We could not validate the Pro membership account information.",
   "required.retpack3.giving_back" : "Please select your third return product.",
   "distinct.password.password_hint" : "Your password cannot be your password hint. Please try again.",
   "obscene.custom_username.macpro_registration" : "Bad language filter. Please enter a new screenname.",
   "format.gc_giftcard_number.giftcard_balance" : "Your Gift Card Number or PIN not recognized. Please re-enter the numbers in the Gift Card number and PIN fields.",
   "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 <a href=\"#\" onClick=\"popWin('/customerservice/cs_max_popup.tmpl', 'max', '516', '250', 'yes')\">Maximum Purchasing Policy</a>.",
   "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 ::offer_code:: is not valid.",
   "pro_discount" : "Pro discount is available on select items.",
   "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.",
   "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.gc_giftcard_number.gc_giftcard_pin" : "Your Gift Card Number or PIN not recognized. Please re-enter the numbers in the Gift Card number and PIN fields.",
   "required.ship_last_name2" : "Please supply your Katagana Last Name.",
   "required.billing_address_id" : "Please select a billing address.",
   "required.retpack1.giving_back" : "Please select your first return product.",
   "required.locator_store_type" : "Please select at least one store type.",
   "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.",
   "message" : "Please limit the length of the giftcard message to 215 characters.",
   "required.email_address" : "Please enter your email address.",
   "unable_to_retrieve_egiftcard" : "We are currently unable to retrieve your eGift Card. Please contact a customer service representative.",
   "required.pro.password_match_existing_acct" : "An account on maccosmetics.com already exists for that email address. Please re-enter a valid password or choose a new email address to be associated with your M·A·C Pro account.",
   "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.",
   "rbe_version" : 0.2,
   "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 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 ::offer_code:: is no longer available.",
   "sku_already_in_collection.add_sku.collection" : " was already added to your favorites.",
   "session" : "",
   "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 can not exceed ::qty_limit:: per order. Please review our <a href=\"cms/customer_service/shoponline.tmpl#max_purchase\"><font color=\"#333333\">Maximum Purchasing Policy</font></a> for more details.",
   "signin_update" : " You must be signed in to update your registration.",
   "invalid.card_number" : "Please enter a valid credit card number.",
   "auto_fss_unmet" : "You are only ::needs:: away from receiving free standard shipping!",
   "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.",
   "length_min.password" : "Please enter a password hint 6 or more characters.",
   "mismatch.card_type.card_number" : "Your credit card type does not match your credit card number.",
   "mpp_discontinued" : "This product has been discontinued and is no longer available online.",
   "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.retpack4.giving_back" : "Please select your fourth return product.",
   "account_signin_to_edit" : "Please sign in to update your registration profile.",
   "order_review_problem" : "Please review your order and correct any problems. ",
   "required.full_name" : "Please enter your name.",
   "expired_offer_code" : "Your offer code has expired.",
   "invalid.email_address" : "Please enter your email address in the following format: jane@aol.com",
   "required.expires_month" : "Please supply the month when your credit card expires. ",
   "ship_postalcode_error" : "Your Address postal code does not match your city/town and prefecture.",
   "required.last_name_alternate" : "Please enter your last name Kana.",
   "sku_unavailable_now" : "This item is currently not available. You will be kept informed via email. This item will be available in ::variable::",
   "required.title" : "Please select your title.",
   "required.pro_id.contact_us required.pro_id.contact_us" : "Please enter your Pro member id.",
   "one_time_use.offer_criteria_not_met" : "The offer code ::offer_code:: has already been redeemed and is no longer valid.",
   "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.",
   "password.hint" : "Please enter your email address to view your password hint",
   "cc_type_id" : "Please select the type of Credit Card. ",
   "identical.password.password2" : "The passwords you have provided do not match. Please re-enter your password.",
   "required.email_address2" : "Please re-enter your email address for verification.",
   "required.account_address" : "Please enter your address.",
   "required.pro_id.macpro_contact_us" : "Please enter your Pro membership ID.",
   "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.customer_type.contact_us" : "Please tell us if you are a US, Canada, or Pro customer.",
   "required.expires_year" : "Please supply the year when your credit card expires.",
   "empty_cart" : "Cart contains no priced items. ",
   "required.retpack2.giving_back" : "Please select your second return product.",
   "required.last_name" : "Please enter your Last Name. ",
   "addr_deleted" : "That address has been deleted from your address book. ",
   "birth_day" : "Please provide your birth day.",
   "duplicate_username.custom_username.macpro_registration" : "That nickname is already in use. Please select another nickname.",
   "new_password_verify" : "The new passwords you have provided do not match. Please re-enter your new password.",
   "required_dependency.macpro_email_promotions.pc_email_address.required_dependency.macpro_member_registration" : "Your email address is required",
   "required.housing_number.contact_us" : "Please enter your house number.",
   "offer_cc_type_id" : "Sorry, offer code ::OFFER_CODE:: is limited to ::REQUIRED_CARD:: customers.",
   "invalid.new_user_email_address" : "Please enter an email address in the following format: jane@aol.com.",
   "required.regarding.macpro_artist_email" : "Please select what your question is regarding.",
   "password_tooshort" : "Please supply a password that is at least 6 characters long.",
   "invalid_emial" : "Please enter your email address in the following format: jane@aol.com ",
   "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.",
   "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.",
   "invalid_zip.zip" : "Your zip code does not match your city and state.",
   "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 state and zip code.",
   "invalid_password.. invalid_password" : "Please review and re-enter your temporary password.",
   "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 State/Province does not match your city and zip/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.",
   "required.password2" : "Please re-enter your password for verification.",
   "changes_saved" : "Your account information has been saved.",
   "locale_switch" : "Please note that products prices, promotions, taxes and ship methods may vary between shipments to US or Canada. Please review your order summary with this in mind.",
   "invalid.password.checkout" : "We do not recognize your sign in information. Please try again. Please note the password field is case sensitive.",
   "identical.email_address.email_address2" : "The email addresses you have provided do not match. Please re-enter your  email address.",
   "macpro_account_not_found" : "The membership information you entered is not valid. Please review your information and try logging in again. If you continue to experience difficulty logging in to the site, please contact us at <a href=\"mailto:promembership@maccosmetics.com\">promembership@maccosmetics.com</a>.",
   "required.ship_phone" : "Please enter your phone number.",
   "required.new_password_verify" : "Please re-enter your new password.",
   "giftcard_error" : "There is a problem with your Gift Card info. Please see below."
};
        
            rb.endeca = {
   "review" : "review",
   "show_all" : "Show all »",
   "search_results" : "Search Results",
   "shop_all_shades" : "Shop All Shades",
   "did_you_mean" : "Did you mean #{didYouMean}?",
   "search_result_term" : "Your search for \"#{searchTerm}\" returned the following results.",
   "no_results" : "No items found matching \"#{searchTerm}\".",
   "no_term" : "Please enter a keyword in the search box on the left side of this page and click SEARCH.",
   "search_again" : "Search again",
   "products" : "Products",
   "no_results_suggestion" : "We suggest you check for typos, broaden your search and enter the type of product you need.",
   "articles" : "Articles",
   "reviews" : "reviews",
   "no_filters" : "No Filters Available.",
   "view_details" : "View Details",
   "no_products" : "There are no product results for your search.",
   "view_all" : "View all",
   "search_within" : "Search within these results"
};
        

var jsTemplates = {
    globalnav : {
           Accordion: '<li id="#{id}"><img id="#{id}_hd" src="#{hdPath}" alt="#{displayName}" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:onClick" class="accordion_hd clickable"><br /><ul id="#{pnavsetChild}" class="accordion_content" style="display:none;" attachPoint="containerNode"></ul></li>          ',
           Panel: '<div class="panel" id="#{id}">    <div class="panelnav_container clearfix">                <div attachPoint="containerNode"></div>    </div></div> ',
           PanelNav: '<li id="#{id}" class="globalnav_hd clickable" attachPoint="containerNode"><img id="#{id}_hd" src="#{hdPath}" alt="#{displayName}" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick"></li>',
           PanelSubNav: '<div id="#{id}" class="panelnav_subnav panelnav_detail_container">    <div attachPoint="progressNode" class="progress"><br></div>    <div attachPoint="containerNode" class="invisible">           </div></div>',
           ProductSubNav : '<div id="#{id}" class="panelnav_subnav">    <div attachPoint="progressNode" class="progress"><br></div>    <div attachPoint="containerNode" class="invisible">        <div id="#{id}_links" class="panelnav_detaillink_container" attachPoint="detailLinksContainerNode">            </div>        <div id="#{id}_cat" class="panelnav_detail_container" attachPoint="detailContainerNode">            </div>        <ul id="#{id}_catlist" class="panelnav_accordion_container hidden" attachPoint="accordionContainerNode">        </ul>    </div></div>',
           SearchSubNav : '<div id="#{id}" class="panelnav_subnav panelnav_detail_container">    <div attachPoint="resultsMessageNode" class="search_results_message"></div>    <div class="search_results_hd"><img id="#{id}_hd" src="images/search/h_top_searches#{imgSuffix}.gif" alt="" /></div>    <div attachPoint="progressNode" class="progress"><br></div>    <div attachPoint="contentResultsContainer" class="hidden search_content_results"><div attachPoint="contentResultsNode"></div>    </div>    <div attachPoint="resultsNode">    </div>    <!--<div id="search_result_pages"></div> note: was result_pgs--></div>',
           DiscontinuedSubNav : '<div id="#{id}" class="panelnav_subnav panelnav_detail_container">    <div attachPoint="progressNode" class="progress"><br></div>    <div attachPoint="containerNode" class="invisible">        <img id="#{id}_hd" class="panelnav_disc_hd" src="images/goodbyes/headers/h_discontinued_prods#{imgSuffix}.gif" width="250" alt="Goodbyes" />        <p attachPoint="panelDescriptionNode" class="panelnav_disc_descr"></p>        <div attachPoint="featuredNode">        </div>                <!--<div id="discontinued_search">            <img src="images/pnav/category/headers/pnav_search_disc_prods_250x18_off#{imgSuffix}.gif" width="250" height="18" alt="Search Discontinued Products" id="disc_search_hd">            <p attachPoint="searchDescriptionNode"></p>            <form onsubmit="return false;">                <input type="text" id="disc_search_input" class="text_field" />                <input type="image" class="btn" id="disc_search_submit" src="images/forms/btn_input.gif" />            </form>            <div id="disc_search_progress" class="progress"><br></div>            <div class="clearfix"></div>        </div>-->                <ul attachPoint="resultsNode" class="search_results">        </ul>        <div attachPoint="resultsMessageNode" class="search_results_message"></div>    </div></div>',
           DiscontinuedProductDetail : '<li id="#{id}" class="panelnav_link" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick">    <a href="#{url}">        <div class="panelnav_detail">            <div class="panelnav_detail_text">                <h3><img id="#{id}_hd" src="#{hdPath}" alt="#{displayName}" /></h3>                <p attachPoint="shadenameNode" class="panelnav_shadename hidden">#{shadename}</p>                             <p attachPoint="descriptionNode">#{description}</p>            </div>            <div class="smoosh_small" style="background-color: #{hex};"><img class="thumb" src="#{thumbPath}" width="56" height="56" alt="#{displayName}" /></div>        </div>    </a></li>',
           SectionDescSubNav : '<div id="#{id}" class="panelnav_subnav panelnav_detail_container">    <div attachPoint="progressNode" class="progress"><br></div>        <div attachPoint="contentNode" class="invisible">        <img attachPoint="hdNode" class="panelnav_section_hd" width="250" />        <p attachPoint="panelDescriptionNode" class="panelnav_section_descr"></p>        <div class="panelnav_detail_container" attachPoint="containerNode">            </div>        </div></div>',
           Detail: '<li id="#{id}" class="panelnav_link panelnav_detailItem #{baseClass}" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick">    <a href="#{url}"><div class="panelnav_detail">        <div class="panelnav_detail_text">            <h3>            <img id="#{id}_hd" src="#{hdPath}" alt="#{displayName}" class="panelnav_detail_hd" /></h3>            <p>#{description}</p>        </div>        <div class="panelnav_thumb"><img id="#{id}_thumb" src="#{thumbPath}" width="56" height="56" alt="" /></div>    </div></a></li>',
           SimpleDetail : '<li id="#{id}" class="panelnav_link panelnav_detailItem #{baseClass}" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick">    <a href="#{url}"><div class="panelnav_detail nothumb">        <div class="panelnav_detail_text">            <h3><img id="#{id}_hd" src="#{hdPath}" class="panelnav_detail_hd" alt="#{displayName}" /></h3>            <p>#{description}</p>        </div>        <div class="panelnav_hspacer"><br /></div>    </div></a></li>',
           CollectionCategoryDetail : '<div id="#{id}"><div id="#{id}_cat" attachPoint="categoryDetailNode" class="panelnav_link" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick"><div class="panelnav_detail"><div class="panelnav_detail_text"><h3><img id="#{id}_hd" src="#{hdPath}" style="height: 18px;" alt="#{displayName}" /></h3><p>#{description}</p></div><div class="panelnav_thumb"><img src="#{thumbPath}" width="56" height="56" alt="" /></div></div></div><div class="clear"><br /></div><ul id="#{id}_catlist" class="hidden" attachPoint="accordionContainerNode"></ul></div>',
           ProductCategoryDetail : '<div id="#{id}" class="panelnav_link" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick">    <div class="panelnav_detail">        <div class="panelnav_detail_text">            <h3><img id="#{id}_hd" src="#{hdPath}" alt="#{displayName}" class="panelnav_catdetail_hd" /></h3>            <p>#{description}</p>        </div>        <div class="panelnav_thumb"><img src="#{thumbPath}" width="56" height="56" alt="" /></div>    </div></div>',
           SearchQuickBuyDetail : '<div id="#{id}" class="panelnav_link panelnav_link_quickbuy panelnav_detailItem" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut">    <div class="panelnav_detail">        <div class="panelnav_detail_text">            <h3><a href="#{url}"><img id="#{id}_hd" src="#{hdPath}" alt="#{displayName}" class="panelnav_detail_hd" /></a></h3>            <a href="#{url}">            <p attachPoint="shadenameNode" class="panelnav_shadename hidden">#{shadename}</p>            <p attachPoint="descriptionNode" class="hidden">#{description}</p>            </a>            <input type="image" src="images/products/btn/btn_add_to_bag_93#{imgSuffix}.gif" id="#{id}_btn_add" value="" class="panelnav_btn_add" />            <span attachPoint="inventoryStatusNode" class="inventory_status"></span>        </div>        <div class="smoosh_small" style="background-color: #{hex};"><a href="#{url}"><img class="thumb" src="#{thumbPath}" alt="#{displayName}" /></a></div>        <div attachPoint="cartConfirmNode"></div>    </div></div>',
           SearchProductDetail : '<div id="#{id}" class="panelnav_link panelnav_link_search panelnav_detailItem" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick">    <a href="#{url}">        <div class="panelnav_detail" attachPoint="panelDetailNode">            <div class="panelnav_detail_text">                <h3><img id="#{id}_hd" src="#{hdPath}" alt="#{displayName}" /></h3>                <p>#{description}</p>                <img id="#{id}_actionimg" src="images/search/btn_view_shades_off#{imgSuffix}.gif" class="panelnav_btn_view_shades">            </div>            <div class="smoosh_small" style="background-color: #{hex};"><img src="#{thumbPath}" width="56" height="56" alt="#{displayName}" /></div>        </div>    </a></div>',
           SearchGiftcard : '<div class="panelnav_link panelnav_link_search" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick">    <a href="#{url}">        <div class="panelnav_detail" attachPoint="panelDetailNode">            <div class="panelnav_detail_text">                <h3><img id="#{id}_hd" src="#{hdPath}" alt="#{displayName}" /></h3>                <p>#{description}</p>                <img src="images/search/btn_view_select_value_off#{imgSuffix}.gif" class="panelnav_btn_view_shades" attachPoint="actionImgNode">            </div>            <div class="smoosh_small" style="background-color: #{hex};"><img src="#{thumbPath}" alt="#{displayName}" /></div>        </div>    </a></div>',
           SearchCustomPalette : '<div class="panelnav_link panelnav_link_search" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick">    <a href="#{url}">        <div class="panelnav_detail" attachPoint="panelDetailNode">            <div class="panelnav_detail_text">                <h3><img id="#{id}_hd" src="#{hdPath}" alt="${displayName}" /></h3>                <p>#{description}</p>                <img src="images/search/btn_custom_palette_off#{imgSuffix}.gif" class="panelnav_btn_view_shades" attachPoint="actionImgNode">            </div>            <div class="smoosh_small" style="background-color: #{hex};"><img src="#{thumbPath}" alt="#{displayName}" /></div>        </div>    </a></div>',
           ContentSearchDetail : '<div id="#{id}" class="panelnav_link panelnav_link_search_content" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut,click:_onClick">    <a href="#{url}"><div class="panelnav_detail nothumb">        <div class="panelnav_detail_text">            <p>#{description}</p>        </div>        <div class="panelnav_hspacer"><br /></div>    </div></a></div> \n',
           headerLi : '<li id="#{id}" class="link_hd clickable" attachpoint="containerNode">    <a href="#{url}"><img src="#{hdPath}" alt="#{displayName}" attachPoint="hdNode"></a></li> \n',
           headerDiv : '<div id="#{id}" class="link_hd clickable" attachpoint="containerNode">    <a href="#{url}"><img src="#{hdPath}" alt="#{displayName}" attachPoint="hdNode"></a></div> \n'
    },
    product : {
           cartAdd : '<div id="#{id}" class="overlay-container cart-add-overlay-container">    <div class="overlay-content-popover popover-prod">        <div class="close-container"><a class="close-link"></a></div>        <img src="#{smooshPath}" width="56" height="56" alt="#{prodName}" class="thumb" id="smoosh_img_#{id}"  style="background-color: #{hex};" />        <div class="popover-desc">            <span class="popover-title">#{prodName}</span>            <span attachpoint="inventoryStatusNode" class="inventory-status"></span>            <p>                <span attachpoint="swatchTitleNode"> </span>                 <span attachpoint="finishNameNode"> </span>            </p>                    #{price}        </div>        <div class="btn-container">            <input class="btn-remove hidden" type="image" src="images/account/btn/btn_pop_remove_white_off#{imgSuffix}.gif" alt="Remove" name="btn_favorites_remove_#{id}" id="btn_favorites_remove_#{id}" value="" attachpoint="removeNode" />            <input class="btn-add" type="image" src="images/popup/btn_add_to_bag#{imgSuffix}.gif" alt="#{addToBag}" name="prod_sku_#{id}" id="prod_sku_#{id}" value="" attachpoint="addToBagNode" />        </div>        <div class="popover-btm"></div>    </div></div>',
           cartConfirm: '<div id="#{id}" class="overlay-container cart-confirm-overlay-container">    <div class="overlay-content-popover popover-message">        <div class="close-container"><a class="close-link"></a></div>        <div attachPoint="cartConfirmDisplayNode">            <div class="popover-desc">                <span class="popover-title thank_you">#{text_thank_you}</span>                <p><span attachPoint="prodNameNode"></span><span attachPoint="shadeNameNode">&nbsp;- </span>&nbsp;<span attachPoint="addedMessageNode"></span></p>                   </div>            <span attachPoint="buttonNodeCheckout"><a href="checkout/viewcart.tmpl"><img src="images/popup/btn_checkout#{imgSuffix}.gif" alt="#{text_checkout}" class="btn-checkout"></a></span>            <span attachPoint="buttonNodeFavorites" class="hidden"><a href="/account/favorites.tmpl"><img src="images/popup/btn_favourites#{imgSuffix}.gif" alt="#{text_favorites}" class="btn-favorites" /></a></span>            <div class="close-link continue-link"><img src="images/popup/btn_continue_shopping#{imgSuffix}.gif" alt="#{text_continue_shopping}" class="btn-continue"></div>        </div>        <div class="popover-desc hidden" attachPoint="cartConfirmErrorNode">             <span class="popover-title sorry">#{text_sorry}</span>            <p><span attachPoint="errorMessageNode"></span></p>              </div>        <div class="popover-btm"></div>    </div></div>',
           hexSwatch : '<div id="#{id}" class="swatch_hex_container" attachEvent="click:_onClick">    <div class="swatch_hex" attachPoint="hexNode" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut" style="background-color: #{hex};"><br /></div>    <div class="tooltip" attachPoint="tooltipNode" style="background-color: #{hex};">#{name} #{inventory_status}</div></div>',
           hexSwatchImage : '<div id="#{id}" class="swatch_hex_container" attachPoint="shadeContainerNode" attachEvent="click:_onClick">    <div class="swatch_hex swatch_hex_smoosh" attachPoint="hexNode" attachEvent="mouseover:_onMouseOver,mouseout:_onMouseOut" style="background-color: #{hex};"><img src="#{smooshThumb}" width="12" height="12" alt=""></div>    <div class="tooltip" attachPoint="tooltipNode" style="background-color: #{hex};">#{name} #{inventory_status}</div></div>',
           thumbSwatch : '<div id="#{id}" class="swatch-thumb-container">    <a href="#" class="swatch-thumb"><img src="#{smooshThumb}" /><span class="tooltip" attachPoint="tooltipNode" style="background-color: #{hex};">#{text_select} #{name} #{text_toshop}</span></a></div>',
           swatchCard : '<div id="#{id}" class="overlay-container swatchcard-container">    <div class="swatchcard">        <img id="smoosh-img-#{id}" class="swatch-lg" src="#{smooshPath}" alt="">        <div class="close-container"><a class="close-link"></a></div>        <div class="card-desc">            <p id="shade-name-#{id}" class="shade-name"> </p>            <p>                <span id="shade-description-#{id}"> </span>&nbsp;                <a href="javascript:void();" class="card-finish"><span id="shade-finish-swatchcards"> </span><span id="shade-finish-description-swatchcards" class="tooltip"> </span></a>            </p>            <div class="card-icons">                <a href="javascript:void(0);" id="limited-flag"><img src="images/products/common/icon_limitedlife.gif" /><span class="tooltip">#{text_limited}</span></a>                <a href="javascript:void(0);" id="pro-flag"><img src="images/products/common/icon_pro.gif"/><span class="tooltip">#{text_macpro}</span></a>            </div>            <p class="card-price">#{price}</p>        </div>        <div id="inventory-status-swatchcards" class="inventory-status"></div>        <input class="btn-add inventory-status-conditional" type="image" src="images/products/btn/btn_add_to_bag_168#{imgSuffix}.gif" alt="" id="prod-sku-#{id}" value="#{skuPath}" />    </div></div>'
    },
    endeca : {
           contentResult : '<li>    <div class="productinfo">        <a href="#{link_url}"><img class="item" src="#{image}"/></a>        <div class="description">            <a href="#{link_url}"><strong class="itemname">#{header_text}</strong></a><br>            #{description}<br>            <a href="#{link_url}">#{link_text}</a>        </div>            </div></li>                    <li><div class="righthr"></div></li>',
           dimensionsBreadcrumbLink : '<li>    <div class="navitem">        <a href="#" class="remove_filter" id="#{dimValueID}-link">    <div class="navtext">        #{Dim Value Name}            </div>        </a>    </div></li>',
           dimensionsNoRefinements : '<h3>No Filters Available.</h3>',
           dimensionsRefinementLink : '<li>        <div class="navitem">    <div class="navtext">        <a href="#" id="#{dimValueID}-link">#{Dim Value Name} (#{Number of Aggregate Records})</a>        </div>    </div></li>',
           dimensionsRefinementLinkShowAll : '<li>        <div class="navitem">    <div class="navtext">        <a href="#" id="#{dimValueID}-link">Show all »</a>        </div>    </div></li>',
           dimensionsRefinements : '<li class="collapsible" id="#{dimID}-header">    <a><h2 class="panelheader #{dimID}">#{Dimension Name}</h2></a>        <ul class="refinement_links" id="#{dimID}-content"></ul></li>',
           productResult : '<li id="#{SKU_ID}-prod">    <div class="productinfo">        <a href="#{url}"><img src="#{image}" class="item"></a>        <a href="#{url}"><strong class="itemname">#{PROD_RGN_NAME}</strong></a>        <div class="itemcost">#{formattedPrice}</div>                <br/>#{SHADENAME}<br/>        <a class="productlink" href="#{url}">#{linkText}</a><br/>        <div style="display:#{RATING_DISPLAY}" class="reviews">            <img src="#{RATING_IMAGE}" class="rating"/>            <img src="images/search/title_overallrating-blk.gif" class="overall_rating"/>            (#{TOTAL_REVIEW_COUNT} #{RATING_REVIEW_WORD})        </div>        <div id="#{SKU_ID}-inventory-status" class="inventory-status addtobag" style="display:none;"></div>        <div id="#{SKU_ID}-add_to_bag" class="addtobag" style="display:none;">            <input type="image" src="images/products/btn/btn_add_to_bag_93.gif" alt="" name="#{SKU_ID}-prod_sku" id="#{SKU_ID}-prod_sku" value="#{CATEGORY_ID}#{PRODUCT_ID}#{SKU_ID}"></input>            <div id="#{SKU_ID}-progress_add_to_bag" class="progress hidden"><br /></div>            <div id="#{SKU_ID}-cart_confirm_placeholder"></div>        </div>    </div></li><li class="righthr"></li>',
           summaryNoResults : '<div id="message">    <p>No items found matching "#{searchTerm}".</p>    <p class="#{didYouMeanClass}">Did you mean #{didYouMean}?</p>    <p>We suggest you check for typos, broaden your search and enter the type of product you need.</p></div>',
           summaryNoTerm : '<div id="message">    <p>Please enter a keyword in the search box on the left side of this page and click SEARCH.</p></div>',
           summaryResults : '<div id="message">    <!--Your search for "#{searchTerm}" returned #{originalRecords} #{resultText}: <a href="#">19 products</a>, <a href="#">1 articles</a>.    <p class="#{searchCorrectedClass}">However, "#{searchCorrected}" returned #{totalRecords} #{resultText}: <a href="#">19 products</a>, <a href="#">1 articles</a></p>-->        <p>Your search for "#{searchTerm}" returned the following results.</p>    <p class="#{didYouMeanClass}">Did you mean #{didYouMean}?</p></div>',
           topContentBanner : '<div id="rightcallout">    <a href="#{link_url}"><img src="#{image}" class="item"/></a>    <div class="description">        <a href="#{link_url}"><img id="rightcallouttitle" src="#{header_image_url}" alt="#{header_text}"/></a><br>        #{description}<br>        <a href="#{link_url}">#{link_text}</a>    </div></div>'
    }
};
/*  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=index.html><\/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}}}}}();/*
	CSS Browser Selector v0.2.9
	Rafael Lima (http://rafael.adm.br)
	http://rafael.adm.br/css_browser_selector
	License: http://creativecommons.org/licenses/by/2.5/
	Contributors: http://rafael.adm.br/css_browser_selector#contributors
*/
var css_browser_selector = function() {var ua=navigator.userAgent.toLowerCase(),is=function(t){return ua.indexOf(t) != -1;},h=document.getElementsByTagName('html')[0],b=(!(/opera|webtv/i.test(ua))&&/msie\s(\d)/.test(ua))?('ie ie'+RegExp.$1):is('firefox/2')?'gecko ff2':is('firefox/3')?'gecko ff3':is('gecko/')?'gecko':is('opera/9')?'opera opera9':/opera\s(\d)/.test(ua)?'opera opera'+RegExp.$1:is('konqueror')?'konqueror':is('chrome')?'chrome webkit safari':is('applewebkit/')?'webkit safari':is('mozilla/')?'gecko':'',os=(is('x11')||is('linux'))?' linux':is('mac')?' mac':is('win')?' win':'';var c=b+os+' js'; h.className += h.className?' '+c:c;}();
 

/**
 * @author Ryan Johnson <ryan@livepipe.net>
 * @copyright 2007 LivePipe LLC
 * @package Control.Tabs
 * @license MIT
 * @url http://livepipe.net/projects/control_tabs/
 * @version 2.1.1
 */

if(typeof(Control) == 'undefined')
	var Control = {};
Control.Tabs = Class.create();
Object.extend(Control.Tabs,{
	instances: [],
	findByTabId: function(id){
		return Control.Tabs.instances.find(function(tab){
			return tab.links.find(function(link){
				return link.key == id;
			});
		});
	}
});
Object.extend(Control.Tabs.prototype,{
	initialize: function(tab_list_container,options){
		this.activeContainer = false;
		this.activeLink = false;
		this.containers = $H({});
		this.links = [];
		Control.Tabs.instances.push(this);
		this.options = {
			beforeChange: Prototype.emptyFunction,
			afterChange: Prototype.emptyFunction,
			hover: false,
			linkSelector: 'li a',
			setClassOnContainer: false,
			activeClassName: 'active',
			defaultTab: 'first',
			autoLinkExternal: true,
			targetRegExp: /#(.+)$/,
			showFunction: Element.show,
			hideFunction: Element.hide
		};
		Object.extend(this.options,options || {});
		(typeof(this.options.linkSelector == 'string')
			? $(tab_list_container).getElementsBySelector(this.options.linkSelector)
			: this.options.linkSelector($(tab_list_container))
		).findAll(function(link){
			return (/^#/).exec(link.href.replace(window.location.href.split('#')[0],''));
		}).each(function(link){
			this.addTab(link);
		}.bind(this));
		this.containers.values().each(this.options.hideFunction);
		if(this.options.defaultTab == 'first')
			this.setActiveTab(this.links.first());
		else if(this.options.defaultTab == 'last')
			this.setActiveTab(this.links.last());
		else
			this.setActiveTab(this.options.defaultTab);
		var targets = this.options.targetRegExp.exec(window.location);
		if(targets && targets[1]){
			targets[1].split(',').each(function(target){
				this.links.each(function(target,link){
					if(link.key == target){
						this.setActiveTab(link);
						throw $break;
					}
				}.bind(this,target));
			}.bind(this));
		}
		if(this.options.autoLinkExternal){
			$A(document.getElementsByTagName('a')).each(function(a){
				if(!this.links.include(a)){
					var clean_href = a.href.replace(window.location.href.split('#')[0],'');
					if(clean_href.substring(0,1) == '#'){
						if(this.containers.keys().include(clean_href.substring(1))){
							$(a).observe('click',function(event,clean_href){
								this.setActiveTab(clean_href.substring(1));
							}.bindAsEventListener(this,clean_href));
						}
					}
				}
			}.bind(this));
		}
	},
	addTab: function(link){
		this.links.push(link);
		link.key = link.getAttribute('href').replace(window.location.href.split('#')[0],'').split('/').last().replace(/#/,'');
		this.containers[link.key] = $(link.key);
		link[this.options.hover ? 'onmouseover' : 'onclick'] = function(link){
			if(window.event)
				Event.stop(window.event);
			this.setActiveTab(link);
			return false;
		}.bind(this,link);
	},
	setActiveTab: function(link){
		if(!link)
			return;
		if(typeof(link) == 'string'){
			this.links.each(function(_link){
				if(_link.key == link){
					this.setActiveTab(_link);
					throw $break;
				}
			}.bind(this));
		}else{
			this.notify('beforeChange',this.activeContainer);
			if(this.activeContainer)
				this.options.hideFunction(this.activeContainer);
			this.links.each(function(item){
				(this.options.setClassOnContainer ? $(item.parentNode) : item).removeClassName(this.options.activeClassName);
			}.bind(this));
			(this.options.setClassOnContainer ? $(link.parentNode) : link).addClassName(this.options.activeClassName);
			this.activeContainer = this.containers[link.key];
			this.activeLink = link;
			this.options.showFunction(this.containers[link.key]);
			this.notify('afterChange',this.containers[link.key]);
		}
	},
	next: function(){
		this.links.each(function(link,i){
			if(this.activeLink == link && this.links[i + 1]){
				this.setActiveTab(this.links[i + 1]);
				throw $break;
			}
		}.bind(this));
		return false;
	},
	previous: function(){
		this.links.each(function(link,i){
			if(this.activeLink == link && this.links[i - 1]){
				this.setActiveTab(this.links[i - 1]);
				throw $break;
			}
		}.bind(this));
		return false;
	},
	first: function(){
		this.setActiveTab(this.links.first());
		return false;
	},
	last: function(){
		this.setActiveTab(this.links.last());
		return false;
	},
	notify: function(event_name){
		try{
			if(this.options[event_name])
				return [this.options[event_name].apply(this.options[event_name],$A(arguments).slice(1))];
		}catch(e){
			if(e != $break)
				throw e;
			else
				return false;
		}
	}
});
if(typeof(Object.Event) != 'undefined')
	Object.Event.extend(Control.Tabs);generic = {     
    init: function() {  
        this.env.debug = this.env.query("debug"); 
        if (this.env.debug && this.env.isIE) console.drawWin(); 
    },
    env: { 
        isIE : !!(typeof(ActiveXObject) == 'function'),
        isIE6 : !!(!!(typeof(ActiveXObject) == 'function') && (/MSIE\s6\.0/.test(navigator.appVersion))),
        isFF : Prototype.Browser.Gecko,
        isFF2 : !!(typeof(navigator.product) != 'undefined' && navigator.product == 'Gecko' && !((document.childNodes) && (!navigator.taintEnabled)) && navigator.userAgent.toLowerCase().split(' firefox/')[1].split('.')[0] == '2'),
        isFF3 : !!(typeof(navigator.product) != 'undefined' && navigator.product == 'Gecko' && !((document.childNodes) && (!navigator.taintEnabled)) && navigator.userAgent.toLowerCase().split(' firefox/')[1].split('.')[0] == '3'),
        isMac    : !!(/macppc|macintel/.test(navigator.platform.toLowerCase())),
        isSafari : !!(/Safari/.test(navigator.userAgent)),
        
        domain : window.location.protocol + "//" + window.location.hostname,
        
        query: function(key) {
            if (typeof generic.env.parsedQuery == "undefined") {
                generic.env.parsedQuery = window.location.href.toQueryParams();
            }
            var result = generic.env.parsedQuery[key] || null;
            return result; 
        },
        
        debug: false
        
    }, 
    helpers: { 
        div: new Element("div") //used by Widget class, Prototype way is needed for IE
    },
    events: {
        target: document,
        fire: function(args) {
            //console.log("generic.events.fire: "+args.event + " / " + args.msg);  
            if (!args) return;
            var e = args.event; 
            var msg = (typeof args.msg == "undefined") ? null : args.msg; 
            generic.events.target.fire(e, {msg:msg});
        },
        observe: function(evt, func) {
            if (!evt || !func) return; 
            generic.events.target.observe(evt, function(e){   
                func(e.memo.msg);
            });
        }   
    },
    forms: {
        select: {
            addOption:  function(args) {
                if (!args || !args.menuNode) return;
                var val = args.value;
                var label = args.label || val;
                var options = args.menuNode.options;
                options[options.length] = new Option(label, val);
            },
            setValue: function(args) {
                var idx = 0;
                for (var i=0, len=args.menuNode.options.length; i<len; i++) {
                    if (args.value == args.menuNode.options[i].value) {
                        idx = i;
                        break;
                    }
                }
                args.menuNode.selectedIndex = idx;
            }
        }
    }
}; 

    
// debug    
if (typeof console === "undefined") {
    if (generic && generic.env.debug) {
        console = {
            tracen : 0,
            win : {},
            drawWin: function() {  
                outp = document.createElement("DIV");
                outp.id = "console-window";
                outp.style.cssText = "position:absolute;top:10px;right:10px;width:400px;height:200px;padding:5px;overflow-x:hidden;overflow-y:scroll;background-color:#ffffff;color:#000000;font-size:12px;border:1px solid red;z-index:9999";
                document.body.appendChild(outp);  
                this.win = $(outp.id);
            },
            log: function(s) {
                if ((typeof this.win != "undefined") && (typeof this.win.innerHTML != "undefined")) {
                    this.tracen++;
                    s = (typeof(s) == "undefined") ? "undefined" : s.toString().replace(/\</gi, "&lt;").replace(/\>/gi, "&gt;");
                    this.win.innerHTML = this.win.innerHTML + "<b>" + this.tracen + "</b>. " + s + "<br/>";
                }
            }    
        }   
    } else {
        console = {
            tracen : 0,
            win : {},
            drawWin: function() {  
                return;
            },
            log: function(s) {
                return;
            }    
        }
    }
};
var generic = generic || {};
generic.rb = generic.rb || {};
var rb = rb || {};

/**
	* This method provides access to resource bundle values that have been 
	* written to the HTML in JSON format. The file that outputs these values
	* must be included in the .html as a script tag with the desired RB name
	* as a query string paramter.
	* It returns an object that exposes one method: get()
	* @example
	* // var myBundle = generic.rb("account");
	* // myBundle.get("err_please_sign_in");
	* @example
    * <script src="/js/shared/v2/internal/resource.tmpl?rb=account"></script>
	* @param {String} rbGroupName name of resource bundle needed
	* @methodOf generic
*/
generic.rb = function(rbGroupName) {
	var findResourceBundle = function(groupName) {
		if (groupName && rb) {
			var rbName = groupName;
			var rbHash = $H(rb[rbName]);
			if (rbHash) {
				return rbHash;
			} else {
				return $H({});
			}
		} else {
			return $H({});
		}
	};
	
	var resourceBundle = findResourceBundle(rbGroupName);
	
	var returnObj = {
    	/**
        * This method will return the value for the requested Resource Bundle key.
        * If the key is not found, the key name will be returned.
    	* @example
    	* // var myBundle = generic.rb("account");
    	* // myBundle.get("err_please_sign_in");
    	* @param {String} keyName key of desired Resource Bundle value
    	*/
		get: function(keyName) {
		    if ( ! Object.isString(keyName) ) {
		        return null;
		    }
			var val = resourceBundle.get(keyName);
			if (val) {
				return val;
			} else {
				return keyName;
			}
		}
	};
	
	return returnObj;
	
};var generic = generic || {};

generic.RediTemplate = Class.create( Template, {
    initialize: function ( template, pattern ) {
        this.template = template?template:'';
        this.readyState = template?1:0;
        this.pattern = pattern?pattern:Template.Pattern; 
        this.queue = new Array();       
        return;
    },
    load: function(template) {
        this.template = template.toString(); 
        this.readyState = 1; 
        this.onReadyState();
    }, 
    evaluateCallback: function (options) {  
        this.options = {
          object:       {},
          callback:     function () {}
        };
        Object.extend(this.options, options || { });  
        if (this.readyState) {   
            this.options.callback(this.evaluate(this.options.object));
        } else { 
            this.queue.push({
                qtype: 'callback',
                obj: this.options.object,
                fnc: this.options.callback
            });
         }
         return;        
    },
    onReadyState: function () {
        while (q = this.queue.shift()) {
            var object = q.obj;
            var qtype = q.qtype;
            var callback = q.fnc;
            var elm;
            callback(this.evaluate(object)); 
        }
    }
}); 

/*
 WDR:
 Some new options to the get() method:
 
 urlparams -- a js hash of stuff to be added to the query string. 
    simple example:

    generic.templatefactory.get({
        path: '/templates/edit-address-jsdata.tmpl',
        urlparams: {
            ADDRESS_ID: options.addrid
        }
    }).evaluateCallback({
        ...blah blah... 
    });

    this results in the url 'http://(domain)/templates/edit-address-jsdata.tmpl?ADDRESS_ID=88888'
    (assuming 88888 is the value of "options.addrid")
    
    From there, the perl side is plain old $request->param('ADDRESS_ID') or whatever...
    
 method -- HTTP method to use.  default is "GET", but you can override as "POST" if desired.
 
 NOTE - The default get() params use the "GET" http method and no query string.
 This leverages the browser cache to save the gotten file for subsequent calls.
 Therefore, understand that using the urlparams and method options may bypass
 the browser cache, which may or may not be your intent.

*/

generic.TemplateFactory = Class.create( Hash, {  
    templatesHash: false,
    get: function (params) {
        var key = params.key || params.path;
        var query = params.query;
        var forceReload = params.forceReload || false; 
        var templateString = params.templateString || false;

        // RediTemplate previously created
        if (typeof this._object[key] != "undefined" && !forceReload && !query) {  
            return this._object[key];
        }
        
        // create RediTemplate 
        this._object[key] = new generic.RediTemplate();  
        
        // check if the path is a key in a hash
        if (this.templateHash) { 
            try { if (key.indexOf(this.templateHash)==0) templateString = eval(key); } catch(e) {}
        }
        
        // check if using a string (either via params.templateString or via the hash)
        if (templateString) {
            this._object[key].load(templateString);
            return this._object[key];
        } 
        
        // get template via Ajax
        var url = key;
        if (query) {
            var q = $H(query);
            var queryString = q.toQueryString();
            url += "?" + queryString;
        }
        var tAjax = new Ajax.Request(url, {
            method: params.method || 'get',
            parameters: params.urlparams,
            onSuccess: function(transport) {   
                this._object[key].load(transport.responseText);
            }.bind(this)
        });
        return this._object[key];
    }   
});
generic.templatefactory = new generic.TemplateFactory();Widget = Class.create({
    setProperties: function(args) {
        Object.extend(this, args);
    }, 
    initialize: function(o) {  
        if (o) this.setProperties(o);  
        //console.log("Widget.init: "+this.id); 
        this.domNode = false;
        this.children = []; 
        
        if (this.templatePath||this.templateString) {
            this.mixInProperties();
        } else { //widget has no template    
            if ($(this.id))  $(this.id).widget = this; 
            this.create();  
        } 
    },
    mixInProperties: function() { 
        //console.log("this.mixInProperties "+this.id+"/"+this.templatePath);
        var key = (this.templateKey ? this.templateKey : this.templatePath);
        var forceReload = (this.forceReload ? this.forceReload : false);
        var params = {key:key, forceReload:forceReload, query: this.query}; 
        if (this.templateString) params.templateString = this.templateString; 
        generic.templatefactory.get(params).evaluateCallback({
            object: this,
            callback: this.handleMixIn.bind(this)
        }); 
    },
    handleMixIn: function(html) { 
        html = html.strip();  
        generic.helpers.div.update(html); 
        this.domNode = generic.helpers.div.firstDescendant();  
    
        var existingContent = false; 
        if ($(this.id)) this.nodeToReplace = $(this.id);
        if (this.nodeToReplace) { //obj with same id might exist, or a different container passed in to be replaced with this node
            //console.log("handleMixIn: this.nodeToReplace "+this.nodeToReplace.id + " /check: "+$("gnav_products"));
            existingContent = this.nodeToReplace.innerHTML; 
            if (this.reinsertNode) {
                //console.log("this.handleMixIn: this.reinsertNode");
                this.nodeToReplace.parentNode.removeChild(this.nodeToReplace); //e.g. psubnav_my_mac
                this.nodeToReplace = false;
            }
        } 
    
        if (this.nodeToReplace) {
            this.updateMixIn();
        } else {
            this.insertMixIn();
        }
        
        var self = this;
        this.domNode.widget = self;     
        this.attachPoints();  
        this.attachEvents();  
        this.containerNode = this.containerNode ? this.containerNode : this.domNode;
        
        if (existingContent) { 
            this.containerNode.insert(existingContent);
        }
        
        this.create(); 
    },
    updateMixIn: function() { 
        //console.log("Widget.updateMixIn: "+this.domNode+" "+this.nodeToReplace.id + " /check: "+$("gnav_products"));
        this.nodeToReplace.parentNode.replaceChild(this.domNode, this.nodeToReplace); 
    },
    insertMixIn: function() {
        if (this.domInsertionMethod) { 
            this.domInsertionMethod(this);
        } else { 
            try {  
                var container = this.domParent ?  this.domParent : $(this.parentId).widget.containerNode;
                if (typeof container == "string") container = $(container); 
                //console.log("this.handleMixIn "+this.id+" // "+container.id);    
                container.insert(this.domNode);   
            } catch(e) {
                console.log("Widget.insertMixIn e: "+this.id+"/"+this.parentId);
            }
        }  
    },
    create: function() {   
        //console.log("Widget.create: "+this.id);   
        if ($(this.parentId) && $(this.parentId).widget) {
            this.parent = $(this.parentId).widget;
            $(this.parentId).widget.children.push(this); 
        } else {
            //console.log("!! Widget.create: "+this.id +" / "+ this.domParent +" / "+ this.parentId +" / "+ $(this.parentId));   
        }       
        
        if (this.postCreate) {
            this.postCreate();   
        } 
    },
    attachPoints: function() { 
        //console.log("Widget.attachPoints "+this.id);    
        var self = this; 
        try {
            var elemsWithPoints = this.domNode.select("[attachPoint]");   
            if (null !== this.domNode.getAttribute("attachPoint")) elemsWithPoints.push(this.domNode);  
            var attachPoint;
        
            elemsWithPoints.each(function(elem) {   
                attachPoints = String(elem.getAttribute("attachPoint")).split(",");  
                attachPoints.each(function(attachPoint){
                    //console.log("Widget.attachPoints: "+elem.id+"/"+attachPoint);  
                    self[attachPoint] = elem; 
                    self["domNode"].widget[attachPoint] = elem;  
                }); 
        
            });
        } catch(e) {
            console.log("Widget.attachPoints e "+ this.domNode + " " +e.description);   
            return;
        }  
    },
    attachEvents: function() { 
        //console.log("Widget.attachEvents: "+this.id);    
        var self = this; 
        try {
            var elemsWithEvents = this.domNode.select("[attachEvent]"); 
            if (null !== this.domNode.getAttribute("attachEvent")) elemsWithEvents.push(this.domNode);  
            var events, etype, functionName, func; 
        
            elemsWithEvents.each(function(elem) { 
                events = String(elem.getAttribute("attachEvent")).split(",");  
                events.each(function(event){
                    event = event.split(":");
                    etype = event[0];
                    functionName = event[1];
                    func = self[functionName];
                    //console.log('Widget.attachEvents: ' + elem.id + '  attached e ' + functionName + ' e type = '+etype+" func = "+func);   
                    //if (func) elem.observe(etype, func.bind(self)); // ie bug
                    if (func) elem["on"+etype] = func.bind(self);
                }); 
            });
        } catch(e) {
            console.log("Widget.attachEvents e "+ this.domNode + " " +e.description);   
            return;
        }  
    }
});var generic = generic || {};

/**
 * This singleton class provides an interface to the Perl Gem JSON-RPC methods via AJAX.
 * @memberOf generic
 */
generic.jsonrpc = ( function() {
    var jsonRpcObj = {

	    id: 0, 
	    url: generic.env.domain + "/rpc/jsonrpc.tmpl",
	    errorCodes: {
	        101: "The data type of this method is not supported.",
	        102: "The data type of the request parameters is not supported.",
	        103: "Your request did not return any results.",
	        104: "Response is not in the expected format."
	    },

		/**
		 * This is the method to use when calling a JSON-RPC method.
		 * @example
		generic.jsonrpc.fetch({
		    method: 'rpc.form',
			"params": [
				{
					"_SUBMIT": "address",
					"COUNTRY_ID": "46",
					"ADDRESS_ID": "9342012",
					"LAST_NAME": "Don",
					"FIRST_NAME": "William"
				}
			]
		    onSuccess: function(jsonRpcResponse) {
		        var responseData = jsonRpcResponse.getData();
				console.dir(responseData);
		    },
		    onFailure: function (jsonRpcResponse) {
		        var errorObjectsArray = jsonRpcResponse.getMessages();
		        var errListNode = addressForm.select("ul.error_messages")[0];
		        generic.showErrors(errorObjectsArray, errListNode, addressForm);
		    }
		}); // end jsonRpcWrapper.fetch
		
         * @param {Array} *Optional* args.params an Array of hashes of method parameters.
         * @param {string|Node} args.method *Optional* name of JSON-RPC method to call. Default value is 'rpc.form'
         * @param {function} args.onSuccess *Optional* callback function. It is invoked with a JsonRpcResponse object as a parameter if the AJAX call returns with HTTP status of 200 AND without a JSON-RPC error.
         * @param {function} args.onFailure *Optional* callback function. It is invoked with a JsonRpcResponse object if the AJAX call returns with HTTP status other than 200. It is also invoked if a 200 response contains a JSON-RPC error.
         * @param {function} args.onBoth *Optional* callback function. If provided, it will override any other callback function passed as a parameter and it will be invoked by any JSON-RPC response.
		 * @memberOf generic.jsonrpc
		 */
	    fetch: function(/* Object*/args) {
	        var self = this;
	        this.id++;

	        var options = {method:'post'};

	        if (args.onBoth) {
	            options.onSuccess = args.onBoth;
	            options.onFailure = args.onBoth;
	        } else {
	            options.onSuccess = args.onSuccess || function (response) {
	                console.log('JSON-RPC success');
	                console.log(Object.toJSON(response.getValue()));
	            };
	            options.onFailure = args.onFailure || function (response) {
	                console.log('JSON-RPC failure');
	                console.log(Object.toJSON(response.getMessages()));
	            };
	        }

	        options.onSuccess = options.onSuccess.wrap(
	            function(proceed, response) {
	                if (!response||!response.responseText) { // empty response
	                    errorHandler(this.createErrorResponse(103));
	                    return;
	                }
	                // Analytics general event for RPC..
	                generic.events.fire({event:'RPC:RESULT',msg:response});

	                var responseArray = response.responseText.evalJSON(true);

	                if (Object.isArray(responseArray)) {
	                    var resultObj = responseArray[0];
	                    if (resultObj) {
	                        var jsonRpcResponse = generic.jsonRpcResponse(resultObj);
	                        if (resultObj.error) { // server returns an error
	                            errorHandler(jsonRpcResponse);
	                        } else if (resultObj.result) { // successful response in expected format
	                            //console.log("generic.jsonrpc.onSuccess");
	                            proceed(jsonRpcResponse);
	                        }
	                    } else { // top-level response array is empty
	                        errorHandler(self.createErrorResponse(103));
	                    }
	                } else { // response is not in expected format (array) 
	                    errorHandler(self.createErrorResponse(104));
	                }
	            });

	        options.onFailure = options.onFailure.wrap( function(proceed, response) {
                var resp = response;
                //server returned failure, i.e. onFailure was not triggered by this class
                if (typeof response.responseText != "undefined") {
                    //console.log("generic.jsonRPC onFailure: server error");
                    try { //server returns an error in json
                        var responseArray = response.responseText.evalJSON(true);
                        var resultObj = responseArray[0];
                        resp = generic.jsonRpcResponse(resultObj);
                    } catch(e) { //server response is not json
                        //console.log("generic.jsonRPC onFailure: server error, result is not json");
                        resp = self.createErrorResponse(response.status,response.responseText);
                    }
                }

                proceed(resp);
            });

	        var errorHandler = options.onFailure;
	        var method = args.method || 'rpc.form';
	        var params = args.params || [];

	        var postObj = {
	            method: method,
	            id: self.id
	        };

	        // make sure a method was passed
	        if ( !Object.isString(method) || method.length <= 0 ) {
	            errorHandler(self.createErrorResponse(101));
	            return null;
	        }

	        //make sure that the params type is an obj
	        if (typeof params === 'string') {
	            postObj.params = params.evalJSON();
	        } else if (typeof params === 'object') {
	            postObj.params = params;
	        } else {
	            errorHandler(self.createErrorResponse(102));
	            return null;
	        }

	        var postString = "[" + Object.toJSON(postObj) + "]";

	        options.parameters = $H({JSONRPC:[postString]}).toQueryString();
	        var url = this.url + '?dbgmethod=' + method;
	        new Ajax.Request( url, options );
	        return this.id;
	    },

	    createErrorResponse: function(errorCode, errorMsg) {
	        errorMsg = errorMsg || this.errorCodes[errorCode];
	        var errorObj = new generic.jsonRpcResponse({
	            "error" : {
	                "code": errorCode,
	                "data": {
	                "messages" : [{
	                    "text" : errorMsg,
	                    "display_locations" : [],
	                    "severity" : "MESSAGE",
	                    "tags" : [],
	                    "key" : ""
	                }]
	                }
	            },
	            "id" : this.id
	        });
	        return errorObj;
	    }
	};
    return jsonRpcObj;
} )(); 


 /**
 * A JsonRpcResponse object is instantiated and returned to the onSuccess and onError
 * callback functions that are passed to the fetch() method. It exposes the contents
 * of the response through its getData, getError, and getMessages methods.
 */
generic.jsonRpcResponse = function (resultObj) {
	var jsonRpcResponseObj = {};
    var rawResponse = resultObj; // raw response data is kept in a private variable
    
    var CartItem = function(itemData) {
        this.product = { 
            sku: {}
        };
        var prodRegEx = /^prod\.(.+)$/;
        var skuRegEx = /sku\.(.+)$/;
        var prodObj = { sku: {} };
        for (var prop in itemData) {
            var newPropName = null;
            var prodResult = prop.match(prodRegEx);
            if (prodResult && prodResult[1]) {
                newPropName = prodResult[1];
                this.product[newPropName] = itemData[prop];
            }
            if (!newPropName) {
                var skuResult = prop.match(skuRegEx);
                if (skuResult && skuResult[1]) {
                    newPropName = skuResult[1];
                    this.product.sku[newPropName] = itemData[prop];
                }
            }
            if (!newPropName) {
                this[prop] = itemData[prop];
            }
        }
    }

	var CartResult = function(responseData) {
	    var data = responseData;
	    var cartItem = {
	        product: { 
	            sku: {}
	        }
	    };
	    var cartMethod;
	    var allItems = [];

	    if (data.ac_results &&
	            Object.isArray(data.ac_results) && 
	            data.ac_results[0]) {
	        if (data.ac_results[0].result &&
	                data.ac_results[0].result.CARTITEM) {
	            cartItem = new CartItem(data.ac_results[0].result.CARTITEM);
	        }
	        if (data.ac_results[0].action) {
	            cartMethod = data.ac_results[0].action;    
	        }
	    }
	    
	    if (data.trans_data &&
	            data.trans_data.order &&
	            Object.isArray(data.trans_data.order.items) ) {
	        data.trans_data.order.items.each(function (itemData) {
	            var tempItem = new CartItem(itemData);
	            allItems.push(tempItem);
	        });
	    }
	    //------------------
	    // PUBLIC METHODS
	    //------------------
	    this.getAllItems = function() {
	        return allItems;
	    }
	    this.getItem = function() {
	        return cartItem;
	    };
	    this.getMethod = function() {
	        return cartMethod;
	    }
	};


    jsonRpcResponseObj.getId = function() {
        if (rawResponse) {
            return rawResponse.id;
        }
        return null;
    };
    jsonRpcResponseObj.getError = function() {
        if (rawResponse &&
            rawResponse.error) {
            return rawResponse.error;
        }
        return null;
    };
    jsonRpcResponseObj.getData = function() {
        if (rawResponse &&
            rawResponse.result &&
            rawResponse.result.data) {
            return rawResponse.result.data;
        }
        return null;
    };
    jsonRpcResponseObj.getValue = function() {
        if (rawResponse &&
            rawResponse.result &&
            typeof rawResponse.result.value != "undefined") {
            return rawResponse.result.value;
        }
        return null;
    };
    /**
     * This method returns the contents of the response's error property.
     * It first checks the result property, then checks the error property.
     */        
    jsonRpcResponseObj.getMessages = function() {
        if (rawResponse) {
            if (rawResponse.result &&
                rawResponse.result.data &&
                rawResponse.result.data.messages) {
                return rawResponse.result.data.messages;
            } else if (rawResponse.error &&
                       rawResponse.error.data &&
                       rawResponse.error.data.messages) {
                return rawResponse.error.data.messages;
            }
        }
        return null;
    };
    jsonRpcResponseObj.getCartResults = function() {
		var data = this.getData();
		if (!data) {
			return null;
		}
		var returnObj = new CartResult(data);
		return returnObj;	
    };

    return jsonRpcResponseObj;
};


generic.cookie = function(/*String*/name, /*String?*/value, /*.__cookieProps*/props){  
	var c = document.cookie; 
	if (arguments.length == 1) { 
		var matches = c.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
		if (matches) {
			matches = decodeURIComponent(matches[1]);
			try {
			     return matches.evalJSON(true); //Object
			} catch(e) {
			     return matches; //String
			}
			
		} else {
			return undefined;
		} 
	} else {
		props = props || {};
// FIXME: expires=0 seems to disappear right away, not on close? (FF3)  Change docs?
		var exp = props.expires;
		if (typeof exp == "number"){ 
			var d = new Date();
			d.setTime(d.getTime() + exp*24*60*60*1000);
			exp = props.expires = d;
		}
		if(exp && exp.toUTCString){ props.expires = exp.toUTCString(); } 
		
		value = encodeURIComponent(value);
		var updatedCookie = name + "=" + value;
		
		for(propName in props){
			updatedCookie += "; " + propName;
			var propValue = props[propName];
			if(propValue !== true){ updatedCookie += "=" + propValue; }
		}
		 
		document.cookie = updatedCookie;
	}
};
	  var generic = generic || {};
generic.flash = {
    abort: false,
    swfObject : swfobject,
    defaults : {
        defaultAlt : { 
            href: "http://www.adobe.com/go/getflashplayer"
        },
        attributes : { 
            playerversion: "9.0.28",
            width: "100%",
            height: "100%", 
                    hspace: 0,
                    vspace: 0,
                    align: "top"
        },
        params : {
            wmode: "transparent", 
                    quality: "high",
                menu: "true",
                    swliveconnect: "true",
                    allowscriptaccess: "always", 
                    scale: "noScale",
                    allowfullscreen: "true"
                } 
        },
    
    embed: function (attributes, params, placeholderId) { 
        if (generic.flash.abort) return;
     
        if (!$(placeholderId)) { 
            console.log("generic.flash.embed: Element doesnt exist"); 
            return; 
        }    
        
        /**
        //BUG: if two swfs are embedded at the same time (with no delay) & there is a "this" reference,
        //the second one is embedded twice.
                attributes = generic.mixin(generic.flash.defaults.attributes,attributes);
                params = generic.mixin(generic.flash.defaults.params,params);
                **/     
                   var defaults = {
        defaultAlt : { 
            href: "http://www.adobe.com/go/getflashplayer"
        },
        attributes : { 
            playerversion: "9.0.28",
            width: "100%",
            height: "100%", 
                    hspace: 0,
                    vspace: 0,
                    align: "top"
        },
        params : {
            wmode: "transparent", 
                    quality: "high",
                menu: "true",
                    swliveconnect: "true",
                    allowscriptaccess: "always", 
                    scale: "noScale",
                    allowfullscreen: "true"
                } 
                 };
                 
                attributes = Object.extend(defaults.attributes, attributes);
                params = Object.extend(defaults.params, params);
                 
            if (typeof params.flashvars != "string") { 
                params.flashvars = Object.toQueryString(params.flashvars); 
            }  
     
        // version check
        if (generic.flash.swfObject.hasFlashPlayerVersion(attributes.playerversion)) {   
            generic.flash.swfObject.addDomLoadEvent(function() { 
                generic.flash.swfObject.createSWF(attributes,params,placeholderId); 
            }); 
            return;
        }
         
        //content if flash doesn't embed
        var altid = attributes.altcontentid; 
        if (altid && $(altid)) {
            altid.style.visibility = "visible";
                    altid.style.display = "block";
        } else { 
            var defaultalt = $(placeholderId).select(".noflash")[0]; 
            if (!defaultalt) return; 
            if (!defaultalt.getAttribute("href")) {
                     defaultalt.observe("click", function() {
                window.open(defaults.defaultAlt.href);
               });
            }
            defaultalt.style.visibility = "visible";
                    defaultalt.style.display = "block";
          
        }     
    },

    /**
     * @namespace favorites contains favorites-related methods that are called by Action Script.
     * @memberOf generic.flash
     */
    favorites: {
        /* Used by Action Script to add items to a collection. Response data
         * is returned via a callback function.
         * @param args {object}
         * @param args.movieName {String} the value of the embed/object tag's name attribute
         * @param args.callback {String} the name that the container (the browser) uses to access the callback Function
         * @param args.skuBaseId {String} the value of the SKU_BASE_ID field for the SKU that is to be added
         * @methodOf generic.flash.favorites
         */
        add: function (args) {
            var options = Object.extend( {
                movieName : "",
                callback: "",
                skuBaseId: ""
            }, args);

            if ( options.skuBaseId.length < 1 ) {
                return null;
            }
            if ( !generic || !generic.checkout || !generic.checkout.cart ) {
                return null;
            }
            var cartObj = generic.checkout.cart;            
            var callbackFn = function(options, responseObj) {
                // console.log(responseObj.getMessages());
                if ( options.movieName.length > 1 &&
                    document[options.movieName] &&
                    document[options.movieName][options.callback] &&
                    typeof document[options.movieName][options.callback] === "function" ) {
                    if (responseObj.getMessages() || responseObj.getError()) {
                        document[options.movieName][options.callback](responseObj.getMessages());                        
                    }
                }
            }.curry(options);
            cartObj.updateCart({
                params: {
                    skus: [options.skuBaseId],           
                    itemType: "favorites",
                    action: "add"
                },
                onSuccess: callbackFn,
                onFailure: callbackFn
            });        
        }
    },

    /**
     * @namespace cart contains cart-related methods that are called by Action Script.
     * @memberOf generic.flash
     */
    cart: {
        /* Used by Action Script to add items to user's shopping cart. Response data
         * is returned via a callback function.
         * @param args {object}
         * @param args.movieName {String} the value of the embed/object tag's name attribute
         * @param args.callback {String} the name that the container (the browser) uses to access the callback Function
         * @param args.skus {Array} the value of the SKU_BASE_ID field for each SKU that is to be added
         * @param args.quantity: {Number} the quantity of items that will be added to the cart.             
         * @methodOf generic.flash.cart
         */
        add: function (args) {
            var options = Object.extend( {
                movieName : "",
                callback: "",
                skus: [],
                quantity: 1
            }, args);
            if ( options.skus.length < 1 ) {
                return null;
            }
            if ( !generic.checkout || !generic.checkout.cart ) {
                return null;
            }
            var cartObj = generic.checkout.cart;            
            var callbackFn = function(options, responseObj) {
                if ( options.movieName.length > 1 &&
                        document[options.movieName] &&
                        document[options.movieName][options.callback] &&
                        typeof document[options.movieName][options.callback] === "function" ) {
                    if (responseObj.getData()) {
                        document[options.movieName][options.callback](responseObj.getData().ac_results);                        
                    } else if (responseObj.getError()) {
                        document[options.movieName][options.callback](responseObj.getMessages());
                    }
                }
            }.curry(options);
            cartObj.updateCart({
                params: {
                    skus: options.skus,            
                    INCREMENT: 1
                },
                onSuccess: callbackFn,
                onFailure: callbackFn
            });         
        }
    }
};generic.checkout = {};

/**
 * generic.checkout.cart 
 * - depends on: generic.cookie, generic.jsonrpc
 */ 
generic.checkout.cart = ( function() {

    return {
        setCookie: false, 
    
        order: new Hash(),
        payments: new Array(),
        carts: new Hash(), 
        items: new Array(),
        samples: new Array(),
     
        totalShoppedItems: 0, 
        totalItems: 0,
     
        transactionParams: {
        transactionFields: {
                    "trans_fields" : ["TRANS_ID", "payments"]
            },
        paymentFields: {
                    "payment_fields" : ["address", "PAYMENT_TYPE", "PAYMENT_AMOUNT", "TRANS_PAYMENT_ID"]
            },
        orderFields: {
                    "order_fields" : ["items", "samples", "address", "TRANS_ORDER_ID"]
            }
        },
    
        itemTypes: { 
            "cart" : {
                "id": "SKU_BASE_ID",
                "_SUBMIT" : "cart"
            },
            "giftcard" : {
                "id": "CART_GIFTCARD_ID",
                "_SUBMIT" : "giftcard"
            },
            "collection" : {
                "id": "SKU_BASE_ID",
                "_SUBMIT" : "collection.items"
            },
            "favorites" : {  
                "id": "SKU_BASE_ID",
                "_SUBMIT" : "alter_collection"
            }
        },

        initialize: function(args) { 
            Object.extend(this, args); // copy args to obj
        },
    
        getCartTotals: function() {
            var cookie = generic.cookie("cart");
            if (cookie && cookie!==null) {
               // console.log("generic.cart.getCartTotals cookie: "+Object.toJSON(cookie)); 
               Object.extend(this, cookie);
           
               generic.events.fire({event:'cart:countsUpdated'});
            } else {
               // console.log("generic.cart.getCartTotals !cookie");  
               this.getCart();
            }  
        },

        // MERGE NOTE: MAC-specific
        //update Cookie, in case Cart has been updated via form submission instead of generic.cart 
        updateCartTotals: function(args) { 
             //console.log("generic.cart.updateCartTotals: "+Object.toJSON(args));  
             Object.extend(this, [args]);
             if (this.ifSetCookie) this.setCookie();    
             generic.events.fire({event:'cart:countsUpdated'});
        },
    
        setCookie: function() { 
            console.log("generic.cart.setCookie "+this.totalItems);
            var s  = {
                totalItems: this.totalItems 
            }
            s = Object.toJSON(s);
            generic.cookie("cart",s, {path:"/"}); 
        },
    
        getCart: function(args) {
            //console.log("generic.cart.getCart");
            var self = this; 

            if (args != null && args.pageDataKey) {
                var pageData = generic.page_data(args.pageDataKey);
                if (pageData.get("rpcdata")) {
                    // console.log( "cart page data found!" );
                    self._updateCartData(pageData.get("rpcdata"));
                    return;
                }
            }

            var params = {};
            params = Object.extend ( params, self.transactionParams.transactionFields );
            params = Object.extend ( params, self.transactionParams.paymentFields);
            params = Object.extend ( params, self.transactionParams.orderFields);
        
             var id = generic.jsonrpc.fetch({
                method : 'trans.get',
                params: [params],
                onSuccess:function(jsonRpcResponse) {
                    self._updateCartData(jsonRpcResponse.getValue()); 
                },
                onFailure: function(jsonRpcResponse){
                    //jsonRpcResponse.getError();
                    console.log('Transaction JSON failed to load');
                }
            });
            return id;
        },
   
         //sets up internal representation of cart state
        // Assuming one order per transaction
        _updateCartData: function(data){ 
            // console.log("generic.checkout.cart._updateCartData");
            var self = this;
            this.data = data;
            this.totalItems = data.items_count; 
            this.defaultCartId = data.default_cart_id;
            this.payments = (data.trans && data.trans.payments) ? $A(data.trans.payments) : null;  
            this.order = data.order; 
         
            // contents and sample_contents mirror the sku by qty hashes
            this.order.contents = new Hash();
            this.order.sample_contents = new Hash();

            if (this.order.items != null) {
                this.order.items = this.order.items.reject(function(item){ // filter out nulls
                    return item === null;
                });
            }
        
            var items = this.order.items || null;
            var totalShoppedItems = 0;
            if (items != null) {  
                items.each(function(item){   
                    if (!item) { return; }
                    totalShoppedItems+=item.ITEM_QUANTITY;
                
                    // set up contents by cart hashes 
                    var cartID = item.CART_ID;
                    var cart = self.carts.get(cartID);
                    if (!cart) {
                        self.carts.set(cartID, new Hash()); 
                        cart = self.carts.get(cartID); 
                        cart.set('contents', new Hash());
                    } 
                    var id = item['sku.SKU_BASE_ID'] ? item['sku.SKU_BASE_ID'] : item.COLLECTION_ID; 
                    cart.get('contents').set(id, item.ITEM_QUANTITY);
                
                    // compute per-unit tax (replace this with field from JSONRPC result when available)
                    var unitTax = item.APPLIED_TAX/item.ITEM_QUANTITY;
                    item.UNIT_TAX = unitTax;

                    // set up order contents hash (spans carts)
                    if (item.itemType.toLowerCase() == 'skuitem') {
                        var key = item['sku.SKU_BASE_ID'];
                        var qty = item.ITEM_QUANTITY; 
                        //error self.order.contents.set(key, qty);
                        self.order.contents[key] = qty; 
                    } else if (item.itemType.toLowerCase() == 'kititem') {
                        var key = item.COLLECTION_ID;
                        var qty = item.ITEM_QUANTITY;
                        self.order.contents.set(key,qty);
                    } else {
                        // FUTURE: other cart item types (e.g. kits)
                    } 
                
                });
            }
        
            this.totalShoppedItems = totalShoppedItems;
        
            var samples = this.order.samples;
            if (samples != null) {
                samples.each(function(item){
                    // set up contents by cart hashes
                    var cartID = item.CART_ID;
                    var cart = self.carts.get(cartID);
                 
                    if (!cart) {
                        self.carts.set(cartID, new Hash());
                        cart = self.carts.get(cartID);
                        cart.set('contents', new Hash());
                    } 
               
                    var id = item['sku.SKU_BASE_ID'] ? item['sku.SKU_BASE_ID'] : item.COLLECTION_ID; 
                    cart.get('contents').set(id, item.ITEM_QUANTITY);

                    // set up order contents hash (spans carts)
                    if (item.itemType.toLowerCase() == 'sampleitem') {
                        var key = item['sku.SKU_BASE_ID'];
                        var qty = item.ITEM_QUANTITY;
                        self.order.sample_contents.set(key,qty); 
                    } else {
                        // other item types (are likely errors)
                    } 
                });
            }
        
            // if (self.setCookie) self.setCookie();        
            generic.events.fire({event:'cart:countsUpdated'});
            // generic.events.fire({event:'cart:updated'}); 
        },
   
        /* args is a hash
        required keys: args.params.skus array, either args.params.INCREMENT or args.QTY with args.INCREMENT overriding args.QTY
        optional keys: args.itemType, args.OFFER_CODE, args.CART_ID*/ 
        updateCart: function(args){
            //console.log("cart.updateCart: "+Object.toJSON(args.params));
        
            if (!args.params) return null; 
            var self = this;   
            var onSuccess = args.onSuccess || Prototype.emptyFunction;  
            var onFailure = args.onFailure || Prototype.emptyFunction; 
            var itemType = args.params.itemType || "cart"; //e.g. cart, collection, giftcard etc
            var id = self.itemTypes[itemType].id;
            var qty = args.params.QTY;
            var action = args.params.action;
            var increment = args.params.INCREMENT;
            var skus = args.params.skus;
         
            var params = {
                '_SUBMIT': self.itemTypes[itemType]["_SUBMIT"] 
            };  

            if (typeof args.params.CAT_BASE_ID != "undefined"){ 
                params["CAT_BASE_ID"] = args.params.CAT_BASE_ID; 
            }else{
                console.log("MISSING CAT_BASE_ID");
            }
            console.log("cat base id in updateCart ", params['CAT_BASE_ID']); 

            //id 
            params[id] = (skus.length == 1) ? skus[0] : skus; //MK collections array syntax correct?
            //qty   
            if (increment && increment>=0) {
               //currently +1 will be added regardless of INCREMENT's actual value
               //backend requires QTY property to exist but it will not be used
             params["INCREMENT"] = increment; 
             params["QTY"] = 1; 
            } else if (increment && increment<0) {
                //decrements qty by -1
            } else if (typeof(qty) !== "undefined" && qty>=0) { 
                params["QTY"] = qty;  
            }
                     
            //offer code
            if (args.params.OFFER_CODE && args.params.OFFER_CODE.length>0) {
                params['OFFER_CODE'] = args.params.OFFER_CODE;
            }
            
            // SS: for some reason this was set to always be "add"
            // need "delete" option for removing favorites
            if (action && (action.length > 0)) {
                params['action'] = action;
            }

            // targeting of the correct cart is still missing (and important to get right) 
            // cart id if we are adding to something other than the default cart
            if (args.params.cart_id && (args.params.cart_id != self.defaultCartId)) {
                params['CART_ID'] = args.params.cart_id;
            }
       
            var id = generic.jsonrpc.fetch({
                "method" : 'rpc.form',
                "params" : [params],
                "onSuccess": function(jsonRpcResponse){
                    var data = jsonRpcResponse.getData();
                    //load data
                    if (data && data["trans_data"]) { 
                        self._updateCartData(data["trans_data"]);
                    }
                    if (itemType === 'cart') {
                        var cartResultObj = jsonRpcResponse.getCartResults();
                        document.fire("cart:updated", cartResultObj);
                    };
                    if (itemType == 'favorites') {
                        document.fire("favorites:updated", jsonRpcResponse);
                    };
                    onSuccess(jsonRpcResponse);
                },
                "onFailure": function(jsonRpcResponse){
                    onFailure(jsonRpcResponse);
                }
            });
 
            return id;
        }, 
    
        getItemQty : function(baseSkuId) {  
            if (!this.order.items) return 0;
            var lineItem = this.order.items.find( function (line) {
                return line['sku.SKU_BASE_ID'] ==  baseSkuId;
              });  
            if (!lineItem) {
                return 0; 
            }                
            return lineItem.ITEM_QUANTITY;
        },
    
        getBaseSkuIds: function() {  //MK: what is this used for?
            //console.log("generic.cart.getBaseSkuIds: "+this.order.items);
            if (!this.order.items) return new Hash();
            var baseSkuIds = this.order.items.pluck( 'sku.SKU_BASE_ID' ); //MK what about giftcards/collections?  
            return baseSkuIds; 
        },  
    
        getSubtotal: function() {
            var lineItems = this.order.items;
            if (!this.order.items) return 0;
            var subtotal = 0;
            for (var i=0, len = lineItems.length; i<len; i++) {
                var lineItem = lineItems[i];
                subtotal += (lineItem.UNIT_PRICE + lineItem.UNIT_TAX) * lineItem.ITEM_QUANTITY;
            }
            return subtotal;
        },
     
        getTotalShoppedItems: function(){ //products and gift cards
           /** var ttl = 0;
            var items = this.order.items;
            if (items != null) {
                items.each(function(item){
                    if (item && item.ITEM_QUANTITY) {
                        ttl += item.ITEM_QUANTITY;
                    }
                });
            } 
            return ttl;**/
            return this.totalShoppedItems;
        },         
        
        getTotalSamples: function() {
             var ttl = 0;
             var samples = this.order.samples;
                if (samples != null) {
                    samples.each(function(item){
                        ttl += item.ITEM_QUANTITY;
                    });
            } 
            return ttl;
        }, 
    
        getTotalItems: function(){ 
           // return this.getTotalShoppedItems() + this.getTotalSamples();
           return this.totalItems;
         }    
     };
}() ) ;

generic.popup = function(/*Object*/args) {
    var activatorNode = $(args.activator);
    if (!activatorNode) { return false; }
        
    //console.log("creating popup for "+activatorNode); 
    var specs = Object.toQueryString(Object.extend(generic.popup.defaults, args));
    var specs = specs.replace(/\&/g, ",");
     
    var open = function() {
        var win = window.open(args.url, args.name, specs);
        if (!win) generic.popup.errorAction(); 
    };

    activatorNode.observe("click", open); 
    
    return true;
}

generic.popup = Object.extend(generic.popup, {
    defaults: {  
        height: 500,
        width: 500,
        top: 25,
        left: 25,
        resizable: "yes",
        scrollbars: "yes",
        status: "no",
        toolbar: "no",
        menubar: "no",
        location: "no"
    },
    errorAction: function() {
         var msg = global.rb.popup_error_message;
         if (msg) alert(msg);
    }
});var extendElement = {
     /* gets element's ancestor with specified tagname/classname **/
     getAncestor: function(element, s){
       	var ancestors = element.ancestors();
		var type = "tagName";
		if (s.substring(0,1)==".") {
			type = "className";
			s = s.substring(1,s.length);
		} 
		var oRegExp = new RegExp("(\\b)" + s + "(\\b)");
		var result = false;
		find(result); 
		return result;
		
		function find(o) { 
			for (var i=0; i<ancestors.length; i++) { 
				if (oRegExp.test((type=="tagName")? ancestors[i].tagName : ancestors[i].className)) {  
					result = ancestors[i]; 
					break;
				}  
			}  
		}
    },
    
    /* gets element's direct children with specified tagname/classname **/	
    getChildren: function(element, s) {
		var children = element.childElements();
		var type = (s.substring(0,1)==".") ? "className" : "tagName";
		s = s.replace(/\./,"");
		var oRegExp = new RegExp("(\\b)" + s + "(\\b)");
		var results = [];
	
		children.each(function($_) {
        	if (oRegExp.test((type=="tagName")? $_.tagName : $_.className)) results.push($_);
       	}); 
		 
		return results;
	}
}
Element.addMethods(extendElement);

 
	 
/**
 * generic.user
 * - depends on: generic.jsonrpc
 */
generic.user = (function(){

    return {
        signed_id : false,

        timeoutLength : 15 * 60 * 1000,

        initialize: function(args) {
            generic.updateProperties.apply(this, [args]);
        },

        getUser: function(args) {
            var self = this;

            if (args != null && args.pageDataKey) {
                var pageData = generic.page_data(args.pageDataKey);
                if (pageData.get("rpcdata")) {
                    console.log( "user page data found!");
                    self._updateUserData(pageData.get("rpcdata"));
                    return;
                }
            }

            var id = generic.jsonrpc.fetch({
                method : 'user.json',
                params: [],
                onSuccess: function(jsonRpcResponse) {
                    self._updateUserData(jsonRpcResponse.getValue());
                },
                onFailure: function(jsonRpcResponse) {
                    console.log('User JSON failed to load');
                }
            });
            return id;
        },

        // until we better parameterise this...
        _updateUserData: function(data) {
            var seld = this;
            if (data != null && data[this.userinfo_rpc_key] != null) {
                Object.extend( this, data[this.userinfo_rpc_key] );
            } else {
                Object.extend( this, data );
            }
            generic.events.fire({event:'user:updated'});
        },

        isSignedIn: function() {
            return ( this.signed_in ? true : false );
        }

    };
}() );

// log out user after 15 minutes on secure pages
if (document.location.protocol == 'https:') {
    var logout = function() {
//        alert(generic.user.timeoutLength);
        document.location.href = 'account/signin.tmpl/timeout=1.html';
    };
    window.setTimeout( logout, generic.user.timeoutLength );
}
var generic = generic || {};

generic.errorStateClassName = 'error';

/**
 * This method displays error messages. It takes an array of error objects and a UL node
 * as parameters. If the UL is not spuulied, it will attempt to find a <UL class="error_messages">
 * in the DOM. It will then attempt to insert one directly after a <DIV id="header"> (If no header
 * is found, the method exits.) All the LI child nodes (previous messages) of the UL are hidden.
 * The text property of each error Hash is then displayed as an LI.
 * This method can also alter the style of the input elements that triggered the error.
 * The tags property in an error hash must be an array that contains a string starting with
 * "field." If the optional formNode parameter is supplied, this form node will be
 * searched for the field, and that field will be passed to the generic.showErrorState method. 
 * @example 
 * var errArray = [
 *      {
 *          "text": "Last Name Alternate must use Kana characters.",
 *          "display_locations": [],
 *          "severity": "MESSAGE",
 *          "tags": ["FORM", "address", "field.last_name_alternate"],
 *          "key": "unicode_script.last_name_alternate.address"
 *      },
 *      {
 *          "text": "First Name Alternate must use Kana characters.",
 *          "display_locations": [],
 *          "severity": "MESSAGE",
 *          "tags": ["FORM", "address", "field.first_name_alternate"],
 *          "key": "unicode_script.first_name_alternate.address"
 *      }
 *  ];
 * var listNode = $$("ul.error_messages")[0];
 * generic.showErrors(errArray, listNode);
 * @param {Array} errorObjectsArray Array of error hashes.
 * @param {DOM Node UL} errListNode UL element in which the error messages will be displayed.
 * @param {DOM Node} formNode Form element (or any container node) that contains the inputs
 * to be marked as being in an error state. 
 */
generic.showErrors = function(errorObjectsArray, errListNode, formNode) {
    var ulNode = errListNode || $$("ul.error_messages")[0];
    if (!ulNode) {
        ulNode = new Element("ul", {"class":"error_messages"});
        var header = $$("div#header")[0];
        if (!header) {
            return null;
        } else {
            header.insert({after:ulNode});
        }
    }
    var errListItemNodes = ulNode.select("li");
    errListItemNodes.each(function(li){
        li.hide();
    });
    ulNode.addClassName("errors-no-messages");
    if (errorObjectsArray.length > 0 && Object.isElement(formNode)){
        // hide all error states on fields
        var inputNodes = formNode.select("input");
        inputNodes = inputNodes.concat(formNode.select("select"));
        inputNodes = inputNodes.concat(formNode.select("label"));
        inputNodes.each(function(inputNode) {
            generic.hideErrorState(inputNode);
        });
    }
    errorObjectsArray.each(function(errObj){
        var errKey = errObj.key;
        var errListItemNode = null;
        if (errKey) {
            var regEx = new RegExp(errKey);
            // try to find LI whose ID matches the error key 
            errListItemNode = errListItemNodes.find(function(node) {
                return regEx.test(node.id);
            });
        }
        if (errListItemNode) {
            errListItemNode.show();
        } else {
            errListItemNode = new Element("li").insert(errObj.text);
            ulNode.insert(errListItemNode);
        }
        if (errObj.displayMode && errObj.displayMode === "message") {
            errListItemNode.addClassName("message");
        }
        if (errObj.tags && Object.isArray(errObj.tags) && formNode) {
            // search through error objects, show error state for any tagged with "field.[NAME]"
            var fieldPrefixRegEx = /^field\.(\w+)$/;
            errObj.tags.each( function(tag) {
                var reResults = tag.match(fieldPrefixRegEx);
                if(reResults && reResults[1]) {
                    var fieldName = reResults[1].toUpperCase();
                    var inputNode = formNode.select("input[name=" + fieldName + "]")[0] || formNode.select("select[name=" + fieldName + "]")[0];
                    if (inputNode) {
                        generic.showErrorState(inputNode);
                        var labelNode = formNode.select("label[for=" + inputNode.id + "]")[0];
                        generic.showErrorState(labelNode);                      
                    }
                }
            });
        }

    });
    ulNode.show();
    if (errorObjectsArray.length > 0){
        ulNode.removeClassName("errors-no-messages");
    }
};
generic.showErrorState = function(inputNode) {
    if (!inputNode || !Object.isElement(inputNode)) {
        return null;
    }
    inputNode.addClassName(generic.errorStateClassName);
}

generic.hideErrorState = function(inputNode) {
    if (!inputNode || !Object.isElement(inputNode)) {
        return null;
    }
    inputNode.removeClassName(generic.errorStateClassName); 
}
var generic = generic || {};
generic.page_data = generic.page_data || {};
var page_data = page_data || {};

generic.page_data = function(pageDataKey) {
    var getPageDataValue = function(pageDataKey) {
        if (!Object.isString(pageDataKey)) {
            return $H({});
        }
        // avoid deep cloning of page_data
        if (pageDataKey && page_data) {
            var key = pageDataKey;
            var parts = key.split(".");
            var length = parts.length;
            var val = page_data;
            var k;
            while (k = parts.shift()) {
                if (val[k]) {
                    val = val[k];
                }
                else {
                    return $H({});
                }
            }
            var rh;
            // For scalars and arrays make a return hash where the key is the pageDataKey
            if (Object.isString(val) || Object.isNumber(val) || Object.isArray(val)) {
                var t = new Object;
                t[pageDataKey] = val;
                rh = $H(t);
            }
            else {
                rh = $H(val);
            }

            if (rh) {
                return rh;
            }
            else {
                return $H({});
            }
        }
        else {
            return $H({});
        }
    };
    var pageDataValue = getPageDataValue(pageDataKey);
    var returnObj = {
        get: function(key) {
            if (!Object.isString(key)) {
                return null;
            }
            var val = pageDataValue.get(key);
            return val;
        }
    };
    return returnObj;
};

var brand = {};

generic.flash.playerversion = (generic.env.isMac) ? "10.0.0" : generic.flash.playerversion;


/**
 * Takes a hex value and returns equivalent rgb value
 * @param {String} hex  
 * @memberOf brand
 * @example
    generic.hexToRGB("#FFFDF4");
 */
brand.hexToRGB = function(hex) {
    var rgb = [];
    if(!hex) return [0, 0, 0];
    var h = cutHex(hex);
    rgb.push( parseInt( h.substring(0,2),16) );
    rgb.push( parseInt( h.substring(2,4),16) );
    rgb.push( parseInt( h.substring(4,6),16) ); 
    //console.log("hexToRGB: "+hex+" = "+rgb[0]+" "+rgb[1]+" "+rgb[2]);
    return rgb;
    
    function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h} 
}

/**
 * brand.updateProperties.apply
 * Needed for cases where Object.extend doesn't return what's expected
 * @memberOf brand
 * @example
    brand.updateProperties.apply(this, [args]);
 */
brand.updateProperties = function(obj) {   
    if (!obj) return;
    for (prop in obj) {  
        this[prop] = obj[prop];
    }  
}


/**
 * brand.tabs
 * Tabbed Container
 * Use this class IF you have created the html manually
 * and you just want to wrap it with the class object.
 * @memberOf brand
 */
brand.tabs = Class.create(Control.Tabs,
{
    // default Control.Tab options
    options: {
        activeClassName: 'tab-active',
        setClassOnContainer: true
    },
       
    initialize: function($super, containerId, args) {
        var options = this.options;
        Object.extend(options, args || {});
        Object.extend(this, args || {});

        if (args.scrollbar) this.initScrolling();        
        $super(containerId, options);
        if (args.useImageHeaders) this.initHeaders();
    },
  
    setActiveTab: function($super, link) {
        if (link.id === this.activeLink.id) return;
        if (this.beforeShow) this.beforeShow(link);
        if (this.scrollbar && this.tabContainer) {
            this.tabContainer.removeClassName(this.scrollbar.enabledClass); // start w/ scrollbar hidden
        }
        if (this.imgHeaders) {
            var linkImg = this.imgHeaders[link.id];
            if (linkImg) linkImg.changeSrc("on");
            var activeLinkImg = this.imgHeaders[this.activeLink.id];
            if (activeLinkImg) activeLinkImg.changeSrc("off");
        }
        
        $super(link);
        this.resetScrolling();
    },

    onContentRefresh: function() {
        this.resetScrolling();
    },

    initHeaders: function() {
        var imgs = {}
        this.links.each(function(link) {
            var imgNode = link.select('img')[0];
            if (imgNode) imgs[link.id] = new brand.img(imgNode, ["on", "off"]);
        });
        this.imgHeaders = imgs;
    },
    
    initScrolling: function() {
        var contentNode = this.scrollbar.contentNode;
        var handleId = this.scrollbar.handleId;
        var trackId = this.scrollbar.trackId;
        if (!contentNode || !handleId || !trackId) return;
        if (!$(handleId) || !$(trackId)) return;
        
        // scroll the element vertically based on its width and the slider maximum value
        var scroll= function(value) {
            contentNode.scrollTop = Math.round(value / scrollbar.maximum * (contentNode.scrollHeight - contentNode.offsetHeight) );
        }
        
        var scrollbar = new Control.Slider(handleId, trackId, {
            axis: 'vertical',
            onSlide: scroll,
            onChange: scroll
        });
        
        this.scrollbarObj = scrollbar;
        this.scrollbarNode = this.scrollbar.containerNode;
    },
    
    resetScrolling: function() {
        var scrollbarObj = this.scrollbarObj;
        if (!this.scrollbar || !scrollbarObj) return;
        var contentNode = this.scrollbar.contentNode;
        var scrollbarNode = this.scrollbar.containerNode;
        // disable/enable scrolling depending on overflow/height of scrollable content
        scrollbarObj.setValue(0);
        if (contentNode.scrollHeight <= contentNode.offsetHeight) {
            scrollbarNode.hide();
            //scrollbarObj.setDisabled();
        } else {
            //scrollbarObj.setEnabled();
            scrollbarNode.show();
            //this.tabContainer.addClassName(this.scrollbar.enabledClass);
        }
    },
    
    updateTab: function(tabId, html) {
        var containerNode = $(tabId);
        if ( containerNode ) {
            containerNode.update ( html );
        }
    }

});

/**
 * brand.bottomFixed
 * simulate fixed bottom position
 * @memberOf brand
 */ 
brand.bottomFixed = Class.create({  
    node: null,  // node: DOM element to position 
    minTop: 0, // minTop: Number minimum pixels for node.style.top 
    isLoaded: false,

    initialize: function(args) {
        if (!args.node) return false;
        this.node = args.node;
        this.s = this.node.style;
        var observeResize = (args.observeResize == false ? false : true);
        if (args.bottom) {
            this.fromBottom = args.bottom;
        } else {
            this.fromBottom = parseInt(this.node.getStyle("bottom"), 10);
        }
        if (isNaN(this.fromBottom)) { 
            console.log("brand.bottomFixed: bottom is NaN");
            return;
        }        
        if (args.minTop) {
            this.minTop = args.minTop;
            this.hasMinTop = true;
        } else {
            this.hasMinTop = false;
        }
        
        // initial top position
        if (args.startingTopPosition == 0 || args.startingTopPosition) {
            this.s.top = args.startingTopPosition + "px";
        } else {
            this.position();
            this.s.bottom = '';
            this.s.visibility = "visible";
        }
        
        // set scroll events
        var self = this;
        Event.observe(window, 'scroll', function() {
            self.onScroll();
        });
        if (observeResize) {
            Event.observe(window, 'resize', function() {
                self.onScroll();
            });
        }

        this.isLoaded = true;
    },
    position: function() {  
        var h = window.pageYOffset ||
        document.documentElement.scrollTop;
        h = (h ? h : 0);
        var shiftY = ((h + document.documentElement.clientHeight) - (this.node.offsetHeight + this.fromBottom));
        if (isNaN(shiftY)) return;
        if (this.currentY != shiftY) {  
            var changeBy = shiftY;      
            if (this.hasMinTop && (changeBy <= this.minTop)) {
                changeBy = this.minTop;
            }
            this.currentY = changeBy;
            this.s.top = (changeBy + 'px');  
        }   
    },
    onScroll: function() {   
        this.position();
    }
});var site = brand;

site.init = function() {
    /** testing **/
    //generic.flash.abort = true;  
    //site.view.colorNav.abort = true; 
    //site.globalnav.abort = true;
    //site.checkout.abort = true;  
    //site.product.abort = true;
    //site.customerService.abort = true;
       
    /** temporary changes **/
    //turn off features not ready for launch

    generic.init(); 
    generic.templatefactory.templateHash = "jsTemplates";

    // brand extention(s) of generic
    generic.overlay = brand.overlay;

    getGlobalRBKeys();
    site.setGlobalParams();
    
    // locale/site extensions:
    // Add file suffix property to all templated widget instances via master Widget class
    Widget.addMethods({ imgSuffix: global.localeFileSuffix });
    
    site.forms.init();
    
    try {
        var id = page_data.panel_nav["default"].id;
    } catch(e) {
        console.log("page_data.panel_nav missing");
        return;
    }
    
    site.globalnav.init();
    site.view.init();
    site.view.utilityNav.initLanguageToggle({ locale: global.locale });
    
    site.account.init();
    site.checkout.init();
    site.product.init();
    site.customerService.init();    
    site.givingBack.init();
    if (!global.ispro) site.livechat.init();
    
    site.overlay.initLinks();
    
    try { // temporary for testing
        site.coremetrics.liveperson.track();
    }
    catch(err) {
        console.log("site.coremetrics.liveperson err = "+err);
    }
};

document.observe("dom:loaded", site.init );


//---------------------------------------------global settings
var global = global || {};
global.locale = "en_us";
global.ispro = false;
global.isprostudent = false;
global.isipad = false;

site.setGlobalParams = function () {
    var proFileSuffix = "";
    if (page_data) {
        if (page_data.customer && page_data.customer.is_pro === 1) {
            global.ispro = true;
            proFileSuffix = "_pro";
            if (page_data.customer.is_pro_student === 1) global.isprostudent = true;
        }
        if (page_data.is_ipad_user_agent && page_data.is_ipad_user_agent == 1) {
            global.isipad = true;
        }
    }
    global.localeFileSuffix = ""; // default
    global.localeFileSuffixes = { // exceptions where needed
        "flash_home" : proFileSuffix
    }
};

//-----------------------------------------------------rb keys
var getGlobalRBKeys = function () {    
    generic.rb.language = generic.rb("language");
    global.rb = global.rb || {};
    global.rb.popup_error_message = generic.rb.language.get("popup_error_message");
    generic.rb.language.rb_close = generic.rb.language.get('close');
    //global.rb.download_flash = generic.rb.language.get("download_flash"); // needed?
};


//-----------------------------------------------------hacks
//cms-output html panel_navs sometimes have this ancient js
//eg. Artists panel or Customer Service panel
var el = function() { return legacy};
var legacy = { addBehavior:function() {}} 
var BehaviorRollover = false;  


//-----------------------------------------------------legacy    
generic.flash.Api = {};
generic.flash.Api.jsCall = function(method, args) { 
    if (generic.flash.ApiMethods[method]) {
        var resp = generic.flash.ApiMethods[method](args);
        return resp;
    } else {}  
};

generic.flash.ApiMethods = { 
    cuePoint: function() {  
        var args = arguments[0];
        generic.events.fire({event:"videoPlayer:cuePoint", msg:args}); 
    //  var inc = Object.toJson(args);
    //  return {"results": args} 
    },

    cuePointProduct: function(args) {
        var passthru = args[0].actions[0];
        this.cuePoint(passthru);
    },

    alterCart: function(args) {   
        console.log("generic.flash.apiMethods.alterCart "+Object.toJSON(args));
        site.checkout.alterCart.alter(args); 
    },

    pageData: function(nargs) {
        console.log("generic.flash.ApiMethods.pageData");
        var args = nargs[0];

        // custom palette 
        if (typeof args === "undefined") {                                                                 
            try {                                                                                      
                if (nargs["query"] && nargs["query"] === "palette" && site.product.customPalette) {                                     
                    site.product.customPalette.pageData(nargs);
                    return;
                }                                                        
            } catch(e) {}                                                                              
        }
         
        var result;
        var pd = parent.page_data;
        if (!pd) return;
        if (args && args.query) {
            //console.log("retrieving page_data with query: " + args.query);
            var path = args.query.split(".");
            var length = path.length;
            var value = pd;
            for (var i = 0; i < length; i++) {
                var key = path.shift();
                value = value[key];
            }
            result = value;
        } else {
            result = pd;
        }
        return { "results": result } 
    }, 
    
    notifyEvent: function(nargs) {    
        site.view.colorNav.setWidth(nargs.event);
    },
    
    setElementSize: function(nargs) {  
        //brand.view.setElementSize(nargs[0]);
        site.view.productBrowser.resizeEmbedContainer(nargs[0]);
    }
};


// cms inline functions
//-----------------------------------------------------

function openFullWindow(url, name, w, h) {
    var w = window.open(url, name, "menubar=1, toolbar=1, resizable=1, scrollbars=1, width=" + w + ", height=" + h);
};

function openLiveChatPopup() {
    if (!site.livechat) return;
    site.livechat.openLiveChatPopup();
};
/**
 * brand.menu
 * Class: Creates a menu whose display is triggerd by mouse events over targetId
 * @memberOf brand
 */
brand.menu = Class.create( {

    // targetId: String
    // id of dom node that triggers display of menu
    targetId: "",
    
    // menuId: String
    // id of menu dom node
    menuId: "",
    
    timer: null,    
    timerDuration: 3,

    /**
     * brand.menu.initialize
     * Method for creating an instance of brand.menu
     * @param {String} args.menu  Menu node Id
     * @param {String} args.target  Id of node which triggers display of menu 
     * @example
        var footerMenu = new brand.menu({
            menu: "countries_container",
            target: "countries_hd"
        });
     * @methodOf brand.menu
     */
    initialize: function(/* Object */args) { 
        this.menuId = args.menu;
        var target = $(args.target);
        var menu = $(this.menuId);
        if (menu && target) {
        this.handlers = [
            target.observe('mouseover', this.show.bind(this)),
            target.observe('mouseout', this.startHide.bind(this)),
            menu.observe('mouseover', this.keepMenu.bind(this)),
            menu.observe('mouseout', this.startHide.bind(this))
        ];
    }
    },
    show: function(e) {
            //console.log("calling  show from "+e.target);
        this.keepMenu(e);
        $(this.menuId).removeClassName("hidden");
    },
    startHide: function(e) {
            //console.log("calling startHide from "+e.target); 
        this.timer = setTimeout(this.hide.bind(this), this.timerDuration);
        Event.stop(e);
        //dojo.stopEvent(e);
    },    
    keepMenu: function(e) {
        //console.log("calling keepMenu from "+e.target);
        clearTimeout(this.timer);
        Event.stop(e);
        //dojo.stopEvent(e);        
    },
    hide: function() { 
        $(this.menuId).addClassName("hidden");
    }
});


/**
 * brand.menuItem
 * line item in a menu
 * @memberOf brand.view
 */
brand.menuItem = Class.create({
    // domNode: Node Object
    // menu item dom node
    domNode: null,
    
    // rolloverClass: String
    // class to apply on mouseover
    rolloverClass: "",

    initialize: function(/* Object */args) { 
        this.domNode = args.domNode;
        this.rolloverClass = args.rolloverClass;
        if (this.domNode) {
            this.domNode.observe('mouseover', this._onMouseOver.bind(this));
            this.domNode.observe('mouseout',  this._onMouseOut.bind(this));
        }
    },
    _onMouseOver: function(e) {
        this.domNode.addClassName(this.rolloverClass);
    },
    _onMouseOut: function(e) {
        this.domNode.removeClassName(this.rolloverClass);
    }
});/**
 * brand.slideshow  
 * @memberOf brand
 */
brand.slideshow = Class.create({
    loop: false, 
    autoStart: false,  
    hasNav: false,  
    interval: 2, 
    looks: null,
    currentSlideIndex: -1,
    totalSlides: 0,
    slides: [],
    header: null,
    slide: null,
    link: null,
      
    initialize: function (args) { 
        if (!args.looks||!args.slide
