<html dir="ltr" lang="en"><head>
    <meta charset="utf-8">
    <meta name="theme-color" content="#fff">
    <meta name="viewport" content="width=device-width, initial-scale=1.0,
                                 maximum-scale=1.0, user-scalable=no">
    <title>drulber.ru</title>
    <style>/* Copyright 2017 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file. */

    a {
        color: var(--link-color);
    }

    body {
        --background-color: #fff;
        --error-code-color: var(--google-gray-700);
        --google-blue-100: rgb(210, 227, 252);
        --google-blue-300: rgb(138, 180, 248);
        --google-blue-600: rgb(26, 115, 232);
        --google-blue-700: rgb(25, 103, 210);
        --google-gray-100: rgb(241, 243, 244);
        --google-gray-300: rgb(218, 220, 224);
        --google-gray-500: rgb(154, 160, 166);
        --google-gray-50: rgb(248, 249, 250);
        --google-gray-600: rgb(128, 134, 139);
        --google-gray-700: rgb(95, 99, 104);
        --google-gray-800: rgb(60, 64, 67);
        --google-gray-900: rgb(32, 33, 36);
        --heading-color: var(--google-gray-900);
        --link-color: rgb(88, 88, 88);
        --popup-container-background-color: rgba(0,0,0,.65);
        --primary-button-fill-color-active: var(--google-blue-700);
        --primary-button-fill-color: var(--google-blue-600);
        --primary-button-text-color: #fff;
        --quiet-background-color: rgb(247, 247, 247);
        --secondary-button-border-color: var(--google-gray-500);
        --secondary-button-fill-color: #fff;
        --secondary-button-hover-border-color: var(--google-gray-600);
        --secondary-button-hover-fill-color: var(--google-gray-50);
        --secondary-button-text-color: var(--google-gray-700);
        --small-link-color: var(--google-gray-700);
        --text-color: var(--google-gray-700);
        background: var(--background-color);
        color: var(--text-color);
        word-wrap: break-word;
    }

    .nav-wrapper .secondary-button {
        background: var(--secondary-button-fill-color);
        border: 1px solid var(--secondary-button-border-color);
        color: var(--secondary-button-text-color);
        float: none;
        margin: 0;
        padding: 8px 16px;
    }

    .hidden {
        display: none;
    }

    html {
        -webkit-text-size-adjust: 100%;
        font-size: 125%;
    }

    .icon {
        background-repeat: no-repeat;
        background-size: 100%;
    }

    @media (prefers-color-scheme: dark) {
        body {
            --background-color: var(--google-gray-900);
            --error-code-color: var(--google-gray-500);
            --heading-color: var(--google-gray-500);
            --link-color: var(--google-blue-300);
            --primary-button-fill-color-active: rgb(129, 162, 208);
            --primary-button-fill-color: var(--google-blue-300);
            --primary-button-text-color: var(--google-gray-900);
            --quiet-background-color: var(--background-color);
            --secondary-button-border-color: var(--google-gray-700);
            --secondary-button-fill-color: var(--google-gray-900);
            --secondary-button-hover-fill-color: rgb(48, 51, 57);
            --secondary-button-text-color: var(--google-blue-300);
            --small-link-color: var(--google-blue-300);
            --text-color: var(--google-gray-500);
        }
    }
    </style>
    <style>/* Copyright 2014 The Chromium Authors. All rights reserved.
   Use of this source code is governed by a BSD-style license that can be
   found in the LICENSE file. */

    button {
        border: 0;
        border-radius: 4px;
        box-sizing: border-box;
        color: var(--primary-button-text-color);
        cursor: pointer;
        float: right;
        font-size: .875em;
        margin: 0;
        padding: 8px 16px;
        transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);
        user-select: none;
    }

    [dir='rtl'] button {
        float: left;
    }

    .bad-clock button,
    .captive-portal button,
    .lookalike-url button,
    .main-frame-blocked button,
    .neterror button,
    .offline button,
    .pdf button,
    .ssl button,
    .safe-browsing-billing button {
        background: var(--primary-button-fill-color);
    }

    button:active {
        background: var(--primary-button-fill-color-active);
        outline: 0;
    }

    #debugging {
        display: inline;
        overflow: auto;
    }

    .debugging-content {
        line-height: 1em;
        margin-bottom: 0;
        margin-top: 1em;
    }

    .debugging-content-fixed-width {
        display: block;
        font-family: monospace;
        font-size: 1.2em;
        margin-top: 0.5em;
    }

    .debugging-title {
        font-weight: bold;
    }

    #details {
        margin: 0 0 50px;
    }

    #details p:not(:first-of-type) {
        margin-top: 20px;
    }

    .secondary-button:active {
        border-color: white;
        box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3),
        0 2px 6px 2px rgba(60, 64, 67, .15);
    }

    .secondary-button:hover {
        background: var(--secondary-button-hover-fill-color);
        border-color: var(--secondary-button-hover-border-color);
        text-decoration: none;
    }

    .error-code {
        color: var(--error-code-color);
        font-size: .86667em;
        text-transform: uppercase;
        margin-top: 12px;
    }

    #error-debugging-info {
        font-size: 0.8em;
    }

    h1 {
        color: var(--heading-color);
        font-size: 1.6em;
        font-weight: normal;
        line-height: 1.25em;
        margin-bottom: 16px;
    }

    h2 {
        font-size: 1.2em;
        font-weight: normal;
    }

    .icon {
        height: 72px;
        margin: 0 0 40px;
        width: 72px;
    }

    input[type=checkbox] {
        opacity: 0;
    }

    input[type=checkbox]:focus ~ .checkbox:after {
        outline: -webkit-focus-ring-color auto 5px;
    }

    .interstitial-wrapper {
        box-sizing: border-box;
        font-size: 1em;
        line-height: 1.6em;
        margin: 14vh auto 0;
        max-width: 600px;
        width: 100%;
    }

    #main-message > p {
        display: inline;
    }

    #extended-reporting-opt-in {
        font-size: .875em;
        margin-top: 32px;
    }

    #extended-reporting-opt-in label {
        display: grid;
        grid-template-columns: 1.8em 1fr;
        position: relative;
    }

    .nav-wrapper {
        margin-top: 51px;
    }

    .nav-wrapper::after {
        clear: both;
        content: '';
        display: table;
        width: 100%;
    }

    .small-link {
        color: var(--small-link-color);
        font-size: .875em;
    }

    .checkboxes {
        flex: 0 0 24px;
    }

    .checkbox {
        --padding: .9em;
        background: transparent;
        display: block;
        height: 1em;
        left: -1em;
        padding-inline-start: var(--padding);
        position: absolute;
        right: 0;
        top: -.5em;
        width: 1em;
    }

    .checkbox::after {
        border: 1px solid white;
        border-radius: 2px;
        content: '';
        height: 1em;
        position: absolute;
        top: var(--padding);
        left: var(--padding);
        width: 1em;
    }

    .checkbox::before {
        background: transparent;
        border: 2px solid white;
        border-right-width: 0;
        border-top-width: 0;
        content: '';
        height: .2em;
        left: calc(.3em + var(--padding));
        opacity: 0;
        position: absolute;
        top: calc(.3em  + var(--padding));
        transform: rotate(-45deg);
        width: .5em;
    }

    input[type=checkbox]:checked ~ .checkbox::before {
        opacity: 1;
    }

    #recurrent-error-message {
        background: #ededed;
        border-radius: 4px;
        padding: 12px 16px;
        margin-top: 12px;
        margin-bottom: 16px;
    }

    .showing-recurrent-error-message #extended-reporting-opt-in {
        margin-top: 16px;
    }

    @media (max-width: 700px) {
        .interstitial-wrapper {
            padding: 0 10%;
        }

        #error-debugging-info {
            overflow: auto;
        }
    }

    @media (max-width: 420px) {
        button,
        [dir='rtl'] button,
        .small-link {
            float: none;
            font-size: .825em;
            font-weight: 500;
            margin: 0;
            width: 100%;
        }

        button {
            padding: 16px 24px;
        }

        #details {
            margin: 20px 0 20px 0;
        }

        #details p:not(:first-of-type) {
            margin-top: 10px;
        }

        .secondary-button:not(.hidden) {
            display: block;
            margin-top: 20px;
            text-align: center;
            width: 100%;
        }

        .interstitial-wrapper {
            padding: 0 5%;
        }

        #extended-reporting-opt-in {
            margin-top: 24px;
        }

        .nav-wrapper {
            margin-top: 30px;
        }
    }

    /**
 * Mobile specific styling.
 * Navigation buttons are anchored to the bottom of the screen.
 * Details message replaces the top content in its own scrollable area.
 */

    @media (max-width: 420px) {
        .nav-wrapper .secondary-button {
            border: 0;
            margin: 16px 0 0;
            margin-inline-end: 0;
            padding-bottom: 16px;
            padding-top: 16px;
        }
    }

    /* Fixed nav. */
    @media (min-width: 240px) and (max-width: 420px) and
    (min-height: 401px),
    (min-width: 421px) and (min-height: 240px) and
    (max-height: 560px) {
        body .nav-wrapper {
            background: var(--background-color);
            bottom: 0;
            box-shadow: 0 -12px 24px var(--background-color);
            left: 0;
            margin: 0 auto;
            max-width: 736px;
            padding-left: 24px;
            padding-right: 24px;
            position: fixed;
            right: 0;
            width: 100%;
            z-index: 2;
        }

        .interstitial-wrapper {
            max-width: 736px;
        }

        #details,
        #main-content {
            padding-bottom: 40px;
        }

        #details {
            padding-top: 5.5vh;
        }

        button.small-link {
            color: var(--google-blue-600);
        }
    }

    @media (max-width: 420px) and (orientation: portrait),
    (max-height: 560px) {
        body {
            margin: 0 auto;
        }

        button,
        [dir='rtl'] button,
        button.small-link,
        .nav-wrapper .secondary-button {
            font-family: Roboto-Regular,Helvetica;
            font-size: .933em;
            margin: 6px 0;
            transform: translatez(0);
        }

        .nav-wrapper {
            box-sizing: border-box;
            padding-bottom: 8px;
            width: 100%;
        }

        #details {
            box-sizing: border-box;
            height: auto;
            margin: 0;
            opacity: 1;
            transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
        }

        #details.hidden,
        #main-content.hidden {
            display: block;
            height: 0;
            opacity: 0;
            overflow: hidden;
            padding-bottom: 0;
            transition: none;
        }

        h1 {
            font-size: 1.5em;
            margin-bottom: 8px;
        }

        .icon {
            margin-bottom: 5.69vh;
        }

        .interstitial-wrapper {
            box-sizing: border-box;
            margin: 7vh auto 12px;
            padding: 0 24px;
            position: relative;
        }

        .interstitial-wrapper p {
            font-size: .95em;
            line-height: 1.61em;
            margin-top: 8px;
        }

        #main-content {
            margin: 0;
            transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1);
        }

        .small-link {
            border: 0;
        }

        .suggested-left > #control-buttons,
        .suggested-right > #control-buttons {
            float: none;
            margin: 0;
        }
    }

    @media (min-width: 421px) and (min-height: 500px) and (max-height: 560px) {
        .interstitial-wrapper {
            margin-top: 10vh;
        }
    }

    @media (min-height: 400px) and (orientation:portrait) {
        .interstitial-wrapper {
            margin-bottom: 145px;
        }
    }

    @media (min-height: 299px) {
        .nav-wrapper {
            padding-bottom: 16px;
        }
    }

    @media (max-height: 560px) and (min-height: 240px) and (orientation:landscape) {
        .extended-reporting-has-checkbox #details {
            padding-bottom: 80px;
        }
    }

    @media (min-height: 500px) and (max-height: 650px) and (max-width: 414px) and
    (orientation: portrait) {
        .interstitial-wrapper {
            margin-top: 7vh;
        }
    }

    @media (min-height: 650px) and (max-width: 414px) and (orientation: portrait) {
        .interstitial-wrapper {
            margin-top: 10vh;
        }
    }

    /* Small mobile screens. No fixed nav. */
    @media (max-height: 400px) and (orientation: portrait),
    (max-height: 239px) and (orientation: landscape),
    (max-width: 419px) and (max-height: 399px) {
        .interstitial-wrapper {
            display: flex;
            flex-direction: column;
            margin-bottom: 0;
        }

        #details {
            flex: 1 1 auto;
            order: 0;
        }

        #main-content {
            flex: 1 1 auto;
            order: 0;
        }

        .nav-wrapper {
            flex: 0 1 auto;
            margin-top: 8px;
            order: 1;
            padding-left: 0;
            padding-right: 0;
            position: relative;
            width: 100%;
        }

        button,
        .nav-wrapper .secondary-button {
            padding: 16px 24px;
        }

        button.small-link {
            color: var(--google-blue-600);
        }
    }

    @media (max-width: 239px) and (orientation: portrait) {
        .nav-wrapper {
            padding-left: 0;
            padding-right: 0;
        }
    }
    </style>
    <style>/* Copyright 2013 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file. */

    /* Don't use the main frame div when the error is in a subframe. */
    html[subframe] #main-frame-error {
        display: none;
    }

    /* Don't use the subframe error div when the error is in a main frame. */
    html:not([subframe]) #sub-frame-error {
        display: none;
    }

    #diagnose-button {
        float: none;
        margin-bottom: 10px;
        margin-inline-start: 0;
        margin-top: 20px;
    }

    h1 {
        margin-top: 0;
        word-wrap: break-word;
    }

    h1 span {
        font-weight: 500;
    }

    h2 {
        color: var(--heading-color);
        font-size: 1.2em;
        font-weight: normal;
        margin: 10px 0;
    }

    a {
        text-decoration: none;
    }

    .icon {
        -webkit-user-select: none;
        display: inline-block;
    }

    .icon-generic {
        /**
   * Can't access browser://theme/IDR_ERROR_NETWORK_GENERIC from an untrusted
   * renderer process, so embed the resource manually.
   */
        content: -webkit-image-set(
                url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAENJREFUeF7tzbEJACEQRNGBLeAasBCza2lLEGx0CxFGG9hBMDDxRy/72O9FMnIFapGylsu1fgoBdkXfUHLrQgdfrlJN1BdYBjQQm3UAAAAASUVORK5CYII=) 1x,
                url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQAQMAAADdiHD7AAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAFJJREFUeF7t0cENgDAMQ9FwYgxG6WjpaIzCCAxQxVggFuDiCvlLOeRdHR9yzjncHVoq3npu+wQUrUuJHylSTmBaespJyJQoObUeyxDQb3bEm5Au81c0pSCD8HYAAAAASUVORK5CYII=) 2x);
    }

    .icon-offline {
        content: -webkit-image-set(
                url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAGxJREFUeF7tyMEJwkAQRuFf5ipMKxYQiJ3Z2nSwrWwBA0+DQZcdxEOueaePp9+dQZFB7GpUcURSVU66yVNFj6LFICatThZB6r/ko/pbRpUgilY0Cbw5sNmb9txGXUKyuH7eV25x39DtJXUNPQGJtWFV+BT/QAAAAABJRU5ErkJggg==) 1x,
                url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQBAMAAAAVaP+LAAAAGFBMVEUAAABTU1NNTU1TU1NPT09SUlJSUlJTU1O8B7DEAAAAB3RSTlMAoArVKvVgBuEdKgAAAJ1JREFUeF7t1TEOwyAMQNG0Q6/UE+RMXD9d/tC6womIFSL9P+MnAYOXeTIzMzMzMzMzaz8J9Ri6HoITmuHXhISE8nEh9yxDh55aCEUoTGbbQwjqHwIkRAEiIaG0+0AA9VBMaE89Rogeoww936MQrWdBr4GN/z0IAdQ6nQ/FIpRXDwHcA+JIJcQowQAlFUA0MfQpXLlVQfkzR4igS6ENjknm/wiaGhsAAAAASUVORK5CYII=) 2x);
        position: relative;
    }

    .icon-disabled {
        content: -webkit-image-set(
                url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHAAAABICAMAAAAZF4G5AAAABlBMVEVMaXFTU1OXUj8tAAAAAXRSTlMAQObYZgAAASZJREFUeAHd11Fq7jAMRGGf/W/6PoWB67YMqv5DybwG/CFjRuR8JBw3+ByiRjgV9W/TJ31P0tBfC6+cj1haUFXKHmVJo5wP98WwQ0ZCbfUc6LQ6VuUBz31ikADkLMkDrfUC4rR6QGW+gF6rx7NaHWCj1Y/W6lf4L7utvgBSt3rBFSS/XBMPUILcJINHCBWYUfpWn4NBi1ZfudIc3rf6/NGEvEA+AsYTJozmXemjXeLZAov+mnkN2HfzXpMSVQDnGw++57qNJ4D1xitA2sJ+VAWMygSEaYf2mYPTjZfk2K8wmP7HLIH5Mg4/pP+PEcDzUvDMvYbs/2NWwPO5vBdMZE4EE5UTQLiBFDaUlTDPBRoJ9HdAYIkIo06og3BNXtCzy7zA1aXk5x+tJARq63eAygAAAABJRU5ErkJggg==) 1x,
                url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOAAAACQAQMAAAArwfVjAAAABlBMVEVMaXFTU1OXUj8tAAAAAXRSTlMAQObYZgAAAYdJREFUeF7F1EFqwzAUBNARAmVj0FZe5QoBH6BX+dn4GlY2PYNzGx/A0CvkCIJuvIraKJKbgBvzf2g62weDGD7CYggpfFReis4J0ey9EGFIiEQQojFSlA9kSIiqd0KkFjKsewgRbStEN19mxUPTtmW9HQ/h6tyqNQ8NlSMZdzyE6qkoE0trVYGFm0n1WYeBhduzwbwBC7voS+vIxfeMjeaiLxsMMtQNwMPtuew+DjzcTHk8YMfDknEcIUOtf2lVfgVH3K4Xv5PRYAXRVMtItIJ3rfaCIVn9DsTH2NxisAVRex2Hh3hX+/mRUR08bAwPEYsI51ZxWH4Q0SpicQRXeyEaIug48FEdegARfMz/tADVsRciwTAxW308ehmC2gLraC+YCbV3QoTZexa+zegAEW5PhhgYfmbvJgcRqngGByOSXdFJcLk2JeDPEN0kxe1JhIt5FiFA+w+ItMELsUyPF2IaJ4aILqb4FbxPwhImwj6JauKgDUCYaxmYIsd4KXdMjIC9ItB5Bn4BNRwsG0XM2nwAAAAASUVORK5CYII=) 2x);
        width: 112px;
    }

    .error-code {
        display: block;
        font-size: .8em;
    }

    #content-top {
        margin: 20px;
    }

    #help-box-inner {
        background-color: #f9f9f9;
        border-top: 1px solid #EEE;
        color: #444;
        padding: 20px;
        text-align: start;
    }

    .hidden {
        display: none;
    }

    #suggestion {
        margin-top: 15px;
    }

    #suggestions-list a {
        color: var(--google-blue-600);
    }

    #suggestions-list p {
        margin-block-end: 0;
    }

    #suggestions-list ul {
        margin-top: 0;
    }

    .single-suggestion {
        list-style-type: none;
        padding-left: 0;
    }

    #short-suggestion {
        margin-top: 5px;
    }

    #error-information-button {
        content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJub25lIiBkPSJNMCAwaDI0djI0SDB6Ii8+PHBhdGggZD0iTTExIDE4aDJ2LTJoLTJ2MnptMS0xNkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTAtMTRjLTIuMjEgMC00IDEuNzktNCA0aDJjMC0xLjEuOS0yIDItMnMyIC45IDIgMmMwIDItMyAxLjc1LTMgNWgyYzAtMi4yNSAzLTIuNSAzLTUgMC0yLjIxLTEuNzktNC00LTR6Ii8+PC9zdmc+);
        height: 24px;
        vertical-align: -.15em;
        width: 24px;
    }

    .use-popup-container#error-information-popup-container
    #error-information-popup {
        align-items: center;
        background-color: var(--popup-container-background-color);
        display: flex;
        height: 100%;
        left: 0;
        position: fixed;
        top: 0;
        width: 100%;
        z-index: 100;
    }

    .use-popup-container#error-information-popup-container
    #error-information-popup-content > p {
        margin-bottom: 11px;
        margin-inline-start: 20px;
    }

    .use-popup-container#error-information-popup-container #suggestions-list ul {
        margin-inline-start: 15px;
    }

    .use-popup-container#error-information-popup-container
    #error-information-popup-box {
        background-color: var(--background-color);
        left: 5%;
        padding-bottom: 15px;
        padding-top: 15px;
        position: fixed;
        width: 90%;
        z-index: 101;
    }

    .use-popup-container#error-information-popup-container div.error-code {
        margin-inline-start: 20px;
    }

    .use-popup-container#error-information-popup-container #suggestions-list p {
        margin-inline-start: 20px;
    }

    :not(.use-popup-container)#error-information-popup-container
    #error-information-popup-close {
        display: none;
    }

    #error-information-popup-close {
        margin-bottom: 0px;
        margin-inline-end: 35px;
        margin-top: 15px;
        text-align: end;
    }

    .link-button {
        color: rgb(66, 133, 244);
        display: inline-block;
        font-weight: bold;
        text-transform: uppercase;
    }

    #sub-frame-error-details {

        color: #8F8F8F;

        /* Not done on mobile for performance reasons. */
        text-shadow: 0 1px 0 rgba(255,255,255,0.3);

    }

    [jscontent=hostName],
    [jscontent=failedUrl] {
        overflow-wrap: break-word;
    }

    #search-container {
        /* Prevents a space between controls. */
        display: flex;
        margin-top: 20px;
    }

    #search-box {
        border: 1px solid #cdcdcd;
        flex-grow: 1;
        font-size: 1em;
        height: 26px;
        margin-right: 0;
        padding: 1px 9px;
    }

    #search-box:focus {
        border: 1px solid rgb(93, 154, 255);
        outline: none;
    }

    #search-button {
        border: none;
        border-bottom-left-radius: 0;
        border-top-left-radius: 0;
        box-shadow: none;
        display: flex;
        height: 30px;
        margin: 0;
        padding: 0;
        width: 60px;
    }

    #search-image {
        content:
                -webkit-image-set(
                        url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAQAAAB+HTb/AAAArElEQVR4Xn3NsUoCUBzG0XvB3U0chR4geo5qihpt6gkCx0bXFsMERWj2KWqIanAvmlUUoQapwU6g4l8H5bd9Z/iSPS0hu/RqZqrncBuzLl7U3Rn4cSpQFTeroejJl1Lgs7f4ceDPdeBMXYp86gaONYJkY83AnqHiGk9wHnjk16PKgo5N9BUCkzPf5j6M0PfuVg5MymoetFwoaKAlB26WdXAvJ7u5mezitqtkT//7Sv/u96CaLQAAAABJRU5ErkJggg==) 1x,
                        url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAeCAQAAACVzLYUAAABYElEQVR4Xr3VMUuVURzH8XO98jgkGikENkRD0KRGDUVDQy0h2SiC4IuIiktL4AvQt1CDBJUJwo1KXXS6cWdHw7tcjWwoC5Hrx+UZgnNO5CXiO/75jD/+QZf9MzjskVU7DrU1zRv9G9ir5hsA4Nii83+GA9ZI1nI1D6tWAE1TRlQMuuuFDthzMQefgo4nKr+f3dIGDdUUHPYD1ISoMQdgJgUfgqaKEOcxWE/BVTArJBvwC0cGY7gNLgiZNsD1GP4EPVn4EtyLYRuczcJ34HYMP4E7GdajDS7FcB48z8AJ8FmI4TjouBkzZ2yBuRQMlsButIZ+dfDVUBqOaIHvavpLVHXfFmAqv45r9gEHNr3y3hcAfLSgSMPgiiZR+6Z9AMuKNAwqpjUcA2h55pxgAfBWkYRlQ254YMJloaxPHbCkiGCymL5RlLA7GnRDXyuC7uhicLoKdRyaDE5Pl00K//93nABqPgBDK8sfWgAAAABJRU5ErkJggg==) 2x);
        margin: auto;
    }

    .secondary-button {
        background: #d9d9d9;
        color: #696969;
        margin-inline-end: 16px;
    }

    .snackbar {
        background: #323232;
        border-radius: 2px;
        bottom: 24px;
        box-sizing: border-box;
        color: #fff;
        font-size: .87em;
        left: 24px;
        max-width: 568px;
        min-width: 288px;
        opacity: 0;
        padding: 16px 24px 12px;
        position: fixed;
        transform: translateY(90px);
        will-change: opacity, transform;
        z-index: 999;
    }

    .snackbar-show {
        -webkit-animation:
                show-snackbar .25s cubic-bezier(0.0, 0.0, 0.2, 1) forwards,
                hide-snackbar .25s cubic-bezier(0.4, 0.0, 1, 1) forwards 5s;
    }

    @-webkit-keyframes show-snackbar {
        100% {
            opacity: 1;
            transform: translateY(0);
        }
    }

    @-webkit-keyframes hide-snackbar {
        0% {
            opacity: 1;
            transform: translateY(0);
        }
        100% {
            opacity: 0;
            transform: translateY(90px);
        }
    }

    .suggestions {
        margin-top: 18px;
    }

    .suggestion-header {
        font-weight: bold;
        margin-bottom: 4px;
    }

    .suggestion-body {
        color: #777;
    }

    /* Increase line height at higher resolutions. */
    @media (min-width: 641px) and (min-height: 641px) {
        #help-box-inner {
            line-height: 18px;
        }
    }

    /* Decrease padding at low sizes. */
    @media (max-width: 640px), (max-height: 640px) {
        h1 {
            margin: 0 0 15px;
        }
        #content-top {
            margin: 15px;
        }
        #help-box-inner {
            padding: 20px;
        }
        .suggestions {
            margin-top: 10px;
        }
        .suggestion-header {
            margin-bottom: 0;
        }
    }

    #download-link, #download-link-clicked {
        margin-bottom: 30px;
        margin-top: 30px;
    }

    #download-link-clicked {
        color: #BBB;
    }

    #download-link:before, #download-link-clicked:before {
        content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxLjJlbSIgaGVpZ2h0PSIxLjJlbSIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNSAyMGgxNHYtMkg1bTE0LTloLTRWM0g5djZINWw3IDcgNy03eiIgZmlsbD0iIzQyODVGNCIvPjwvc3ZnPg==);
        display: inline-block;
        margin-inline-end: 4px;
        vertical-align: -webkit-baseline-middle;
    }

    #download-link-clicked:before {
        width: 0px;
        opacity: 0;
    }

    #offline-content-list-visibility-card {
        border: 1px solid white;
        border-radius: 8px;
        display: flex;
        font-size: .8em;
        justify-content: space-between;
        line-height: 1;
    }

    #offline-content-list.list-hidden #offline-content-list-visibility-card {
        border-color: rgb(218, 220, 224);
    }

    #offline-content-list-visibility-card > div {
        padding: 1em;
    }

    #offline-content-list-title {
        color: var(--google-gray-700);
    }

    #offline-content-list-show-text, #offline-content-list-hide-text {
        color: rgb(66, 133, 244);
    }

    /* Hides the "hide" text div when the offline content list is collapsed/hidden
 * and, alternatively, hides the "show" text div when the offline content list
 * is expanded/shown.
 */
    #offline-content-list.list-hidden #offline-content-list-hide-text,
    #offline-content-list:not(.list-hidden) #offline-content-list-show-text {
        display: none;
    }

    /* Controls the animation of the offline content list when it is expanded/shown.
 */
    #offline-content-suggestions {
        /* Max-height has to be set for the height animation to work. The chosen value
   * is a little greater than the maximum height the list will have, when all
   * suggestions have images, so that it is never clamped. This makes so that
   * when the actual height is smaller then the animation is not as smooth.
   */
        max-height: 27em;
        transition: max-height 0.2s ease-in, visibility 0s 0.2s,
        opacity 0.2s 0.2s linear;
    }

    /* Controls the animation of the offline content list when it is
 * collapsed/hidden.
 */
    #offline-content-list.list-hidden #offline-content-suggestions {
        max-height: 0;
        visibility: hidden;
        opacity: 0;
        transition: opacity 0.2s linear, visibility 0s 0.2s,
        max-height 0.2s 0.2s ease-out;
    }

    #offline-content-list {
        margin-inline-start: -5%;
        width: 110%;
    }

    /* The selectors below adjust the "overflow" of the suggestion cards contents
 * based on the same screen size based strategy used for the main frame, which
 * is applied by the `interstitial-wrapper` class. */
    @media (max-width: 420px)  {
        #offline-content-list {
            margin-inline-start: -2.5%;
            width: 105%;
        }
    }
    @media (max-width: 420px) and (orientation: portrait),
    (max-height: 560px) {
        #offline-content-list {
            margin-inline-start: -12px;
            width: calc(100% + 24px);
        }
    }

    .suggestion-with-image .offline-content-suggestion-thumbnail {
        flex-basis: 8.2em;
        flex-shrink: 0;
    }

    .suggestion-with-image .offline-content-suggestion-thumbnail > img {
        height: 100%;
        width: 100%;
    }

    .suggestion-with-image #offline-content-list:not(.is-rtl)
    .offline-content-suggestion-thumbnail > img {
        border-bottom-right-radius: 7px;
        border-top-right-radius: 7px;
    }

    .suggestion-with-image #offline-content-list.is-rtl
    .offline-content-suggestion-thumbnail > img {
        border-bottom-left-radius: 7px;
        border-top-left-radius: 7px;
    }

    .suggestion-with-icon .offline-content-suggestion-thumbnail {
        align-items: center;
        display: flex;
        justify-content: center;
        min-height: 4.2em;
        min-width: 4.2em;
    }

    .suggestion-with-icon .offline-content-suggestion-thumbnail > div {
        align-items: center;
        background-color: rgb(241, 243, 244);
        border-radius: 50%;
        display: flex;
        height: 2.3em;
        justify-content: center;
        width: 2.3em;
    }

    .suggestion-with-icon .offline-content-suggestion-thumbnail > div > img {
        height: 1.45em;
        width: 1.45em;
    }

    .offline-content-suggestion-favicon {
        height: 1em;
        margin-inline-end: 0.4em;
        width: 1.4em;
    }

    .offline-content-suggestion-favicon > img {
        height: 1.4em;
        width: 1.4em;
    }

    .no-favicon .offline-content-suggestion-favicon {
        display: none;
    }

    .image-video {
        content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTcgMTAuNVY3YTEgMSAwIDAgMC0xLTFINGExIDEgMCAwIDAtMSAxdjEwYTEgMSAwIDAgMCAxIDFoMTJhMSAxIDAgMCAwIDEtMXYtMy41bDQgNHYtMTFsLTQgNHoiIGZpbGw9IiMzQzQwNDMiLz48L3N2Zz4=);
    }

    .image-music-note {
        content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgM3Y5LjI2Yy0uNS0uMTctMS0uMjYtMS41LS4yNkM4IDEyIDYgMTQgNiAxNi41UzggMjEgMTAuNSAyMXM0LjUtMiA0LjUtNC41VjZoNFYzaC03eiIgZmlsbD0iIzNDNDA0MyIvPjwvc3ZnPg==);
    }

    .image-earth {
        content: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMmM1LjUyIDAgMTAgNC40OCAxMCAxMHMtNC40OCAxMC0xMCAxMFMyIDE3LjUyIDIgMTIgNi40OCAyIDEyIDJ6TTQgMTJoNC40YzMuNDA3LjAyMiA0LjkyMiAxLjczIDQuNTQzIDUuMTI3SDkuNDg4djIuNDdhOC4wMDQgOC4wMDQgMCAwIDAgMTAuNDk4LTguMDgzQzE5LjMyNyAxMi41MDQgMTguMzMyIDEzIDE3IDEzYy0yLjEzNyAwLTMuMjA2LS45MTYtMy4yMDYtMi43NWgtMy43NDhjLS4yNzQtMi43MjguNjgzLTQuMDkyIDIuODctNC4wOTIgMC0uOTc1LjMyNy0xLjU5Ny44MTEtMS45N0E4LjAwNCA4LjAwNCAwIDAgMCA0IDEyeiIgZmlsbD0iIzNDNDA0MyIvPjwvc3ZnPg==);
    }

    .image-file {
        content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTMgOVYzLjVMMTguNSA5TTYgMmMtMS4xMSAwLTIgLjg5LTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWOGwtNi02SDZ6IiBmaWxsPSIjM0M0MDQzIi8+PC9zdmc+);
    }

    .offline-content-suggestion-texts {
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        line-height: 1.3;
        padding: .9em;
        width: 100%;
    }

    .offline-content-suggestion-title {
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        color: rgb(32, 33, 36);
        display: -webkit-box;
        font-size: 1.1em;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    div.offline-content-suggestion {
        align-items: stretch;
        border: 1px solid rgb(218, 220, 224);
        border-radius: 8px;
        display: flex;
        justify-content: space-between;
        margin-bottom: .8em;
    }

    .suggestion-with-image {
        flex-direction: row;
        height: 8.2em;
        max-height: 8.2em;
    }

    .suggestion-with-icon {
        flex-direction: row-reverse;
        height: 4.2em;
        max-height: 4.2em;
    }

    .suggestion-with-icon .offline-content-suggestion-title {
        -webkit-line-clamp: 1;
        word-break: break-all;
    }

    .suggestion-with-icon .offline-content-suggestion-texts {
        padding-inline-start: 0px;
    }

    .offline-content-suggestion-attribution-freshness {
        color: rgb(95, 99, 104);
        display: flex;
        font-size: .8em;
        line-height: 1.7em;
    }

    .offline-content-suggestion-attribution {
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 1;
        display: -webkit-box;
        flex-shrink: 1;
        margin-inline-end: 0.3em;
        overflow-wrap: break-word;
        overflow: hidden;
        text-overflow: ellipsis;
        word-break: break-all;
    }

    .no-attribution .offline-content-suggestion-attribution {
        display: none;
    }

    .offline-content-suggestion-freshness:before {
        content: '-';
        display: inline-block;
        flex-shrink: 0;
        margin-inline-end: .1em;
        margin-inline-start: .1em;
    }

    .no-attribution .offline-content-suggestion-freshness:before {
        display: none;
    }

    .offline-content-suggestion-freshness {
        flex-shrink: 0;
    }

    .suggestion-with-image .offline-content-suggestion-pin-spacer {
        flex-shrink: 1;
        flex-grow: 100;
    }

    .suggestion-with-image .offline-content-suggestion-pin {
        content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PGRlZnM+PHBhdGggaWQ9ImEiIGQ9Ik0wIDBoMjR2MjRIMFYweiIvPjwvZGVmcz48Y2xpcFBhdGggaWQ9ImIiPjx1c2UgeGxpbms6aHJlZj0iI2EiIG92ZXJmbG93PSJ2aXNpYmxlIi8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjYikiIGQ9Ik0xMiAyQzYuNSAyIDIgNi41IDIgMTJzNC41IDEwIDEwIDEwIDEwLTQuNSAxMC0xMFMxNy41IDIgMTIgMnptNSAxNkg3di0yaDEwdjJ6bS02LjctNEw3IDEwLjdsMS40LTEuNCAxLjkgMS45IDUuMy01LjNMMTcgNy4zIDEwLjMgMTR6IiBmaWxsPSIjOUFBMEE2Ii8+PC9zdmc+);
        flex-shrink: 0;
        height: 1.4em;
        margin-inline-start: .4em;
        width: 1.4em;
    }

    /* Controls the animation (and a bit more) of the launch-downloads-home action
 * button when the offline content list is expanded/shown.
 */
    #offline-content-list-action {
        text-align: center;
        transition: visibility 0s 0.2s, opacity 0.2s 0.2s linear;
    }

    /* Controls the animation of the launch-downloads-home action button when the
 * offline content list is collapsed/hidden.
 */
    #offline-content-list.list-hidden #offline-content-list-action {
        visibility: hidden;
        opacity: 0;
        transition: opacity 0.2s linear, visibility 0s 0.2s;
    }

    #cancel-save-page-button {
        background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48Y2xpcFBhdGggaWQ9Im1hc2siPjxwYXRoIGQ9Ik0xMiAyQzYuNSAyIDIgNi41IDIgMTJzNC41IDEwIDEwIDEwIDEwLTQuNSAxMC0xMFMxNy41IDIgMTIgMnptNSAxNkg3di0yaDEwdjJ6bS02LjctNEw3IDEwLjdsMS40LTEuNCAxLjkgMS45IDUuMy01LjNMMTcgNy4zIDEwLjMgMTR6IiBmaWxsPSIjOUFBMEE2Ii8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjbWFzaykiIGZpbGw9IiM5QUEwQTYiIGQ9Ik0wIDBoMjR2MjRIMHoiLz48cGF0aCBjbGlwLXBhdGg9InVybCgjbWFzaykiIGZpbGw9IiMxQTczRTgiIHN0eWxlPSJhbmltYXRpb246b2ZmbGluZUFuaW1hdGlvbiA0cyBpbmZpbml0ZSIgZD0iTTAgMGgyNHYyNEgweiIvPjxzdHlsZT5Aa2V5ZnJhbWVzIG9mZmxpbmVBbmltYXRpb257MCUsMzUle2hlaWdodDowfTYwJXtoZWlnaHQ6MTAwJX05MCV7ZmlsbC1vcGFjaXR5OjF9dG97ZmlsbC1vcGFjaXR5OjB9fTwvc3R5bGU+PC9zdmc+);
        background-position: right 27px center;
        background-repeat: no-repeat;
        border: 1px solid var(--google-gray-300);
        border-radius: 5px;
        color: var(--google-gray-700);
        margin-bottom: 26px;
        padding-bottom: 16px;
        padding-inline-end: 88px;
        padding-inline-start: 16px;
        padding-top: 16px;
        text-align: start;
    }

    html[dir="rtl"] #cancel-save-page-button {
        background-position: left 27px center;
    }

    #save-page-for-later-button {
        display: flex;
        justify-content: start;
    }

    #save-page-for-later-button a:before {
        content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxLjJlbSIgaGVpZ2h0PSIxLjJlbSIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNSAyMGgxNHYtMkg1bTE0LTloLTRWM0g5djZINWw3IDcgNy03eiIgZmlsbD0iIzQyODVGNCIvPjwvc3ZnPg==);
        display: inline-block;
        margin-inline-end: 4px;
        vertical-align: -webkit-baseline-middle;
    }

    .hidden#save-page-for-later-button {
        display: none;
    }

    /* Don't allow overflow when in a subframe. */
    html[subframe] body {
        overflow: hidden;
    }

    #sub-frame-error {
        -webkit-align-items: center;
        background-color: #DDD;
        display: -webkit-flex;
        -webkit-flex-flow: column;
        height: 100%;
        -webkit-justify-content: center;
        left: 0;
        position: absolute;
        text-align: center;
        top: 0;
        transition: background-color .2s ease-in-out;
        width: 100%;
    }

    #sub-frame-error:hover {
        background-color: #EEE;
    }

    #sub-frame-error .icon-generic {
        margin: 0 0 16px;
    }

    #sub-frame-error-details {
        margin: 0 10px;
        text-align: center;
        visibility: hidden;
    }

    /* Show details only when hovering. */
    #sub-frame-error:hover #sub-frame-error-details {
        visibility: visible;
    }

    /* If the iframe is too small, always hide the error code. */
    /* TODO(mmenke): See if overflow: no-display works better, once supported. */
    @media (max-width: 200px), (max-height: 95px) {
        #sub-frame-error-details {
            display: none;
        }
    }

    /* Adjust icon for small embedded frames in apps. */
    @media (max-height: 100px) {
        #sub-frame-error .icon-generic {
            height: auto;
            margin: 0;
            padding-top: 0;
            width: 25px;
        }
    }

    /* details-button is special; it's a <button> element that looks like a link. */
    #details-button {
        box-shadow: none;
        min-width: 0;
    }

    /* Styles for platform dependent separation of controls and details button. */
    .suggested-left > #control-buttons,
    .suggested-right > #details-button {
        float: left;
    }

    .suggested-right > #control-buttons,
    .suggested-left > #details-button {
        float: right;
    }

    .suggested-left .secondary-button {
        margin-inline-end: 0px;
        margin-inline-start: 16px;
    }

    #details-button.singular {
        float: none;
    }

    /* download-button shows both icon and text. */
    #download-button {
        padding-bottom: 4px;
        padding-top: 4px;
        position: relative;
    }

    #download-button:before {
        background: -webkit-image-set(
                url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAO0lEQVQ4y2NgGArgPxIY1YChsOE/LtBAmpYG0mxpIOSDBpKUo2lpIDZxNJCkHKqlYZAla3RAHQ1DFgAARRroHyLNTwwAAAAASUVORK5CYII=) 1x,
                url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAAZElEQVRYw+3Ruw3AMAwDUY3OzZUmRRD4E9iim9wNwAdbEURHyk4AAAAATiCVK8lLyPsKeT9K3lsownnunfkPxO78hKiYHxBV8x2icr5BVM+/CMf8g3DN34Rzns6ViwHUAUQ/6wIAd5Km7l6c8AAAAABJRU5ErkJggg==) 2x)
        no-repeat;
        content: '';
        display: inline-block;
        width: 24px;
        height: 24px;
        margin-inline-end: 4px;
        margin-inline-start: -4px;
        vertical-align: middle;
    }

    #download-button:disabled {
        background: rgb(180, 206, 249);
        color: rgb(255, 255, 255);
    }

    /*
TODO(https://crbug.com/852872): UI for offline suggested content is incomplete.
*/
    .suggested-thumbnail {
        width: 25vw;
        height: 25vw;
    }

    /* Alternate dino page button styles */
    #control-buttons .reload-button-alternate:disabled {
        background: #ccc;
        color: #fff;
        font-size: 14px;
        height: 48px;
    }

    #buttons::after {
        clear: both;
        content: '';
        display: block;
        width: 100%;
    }

    /* Offline page */
    .offline {
        transition: filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1),
        background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);

        will-change: filter, background-color;

    }

    .offline body {
        transition: background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);
    }

    .offline #main-message > p {
        display: none;
    }

    /* iOS WKWebView inverts the background color set at the HTML level
whereas Blink does not. */
    .offline.inverted {
        filter: invert(1);

        background-color: #000;


    }

    .offline.inverted body {
        background-color: #fff;
    }

    .offline .interstitial-wrapper {
        color: var(--text-color);
        font-size: 1em;
        line-height: 1.55;
        margin: 0 auto;
        max-width: 600px;
        padding-top: 100px;
        width: 100%;
    }

    .offline .runner-container {
        direction: ltr;
        height: 150px;
        max-width: 600px;
        overflow: hidden;
        position: absolute;
        top: 35px;
        width: 44px;
    }

    .offline .runner-canvas {
        height: 150px;
        max-width: 600px;
        opacity: 1;
        overflow: hidden;
        position: absolute;
        top: 0;
        z-index: 10;
    }

    .offline .controller {
        background: rgba(247,247,247, .1);
        height: 100vh;
        left: 0;
        position: absolute;
        top: 0;
        width: 100vw;
        z-index: 9;
    }

    #offline-resources {
        display: none;
    }

    #offline-instruction {
        image-rendering: pixelated;
        left: 0;
        margin: auto;
        position: absolute;
        right: 0;
        top: 60px;
        width: fit-content;
    }

    @media (max-width: 420px) {
        #download-button {
            padding-bottom: 12px;
            padding-top: 12px;
        }

        .suggested-left > #control-buttons,
        .suggested-right > #control-buttons {
            float: none;
        }

        .snackbar {
            left: 0;
            bottom: 0;
            width: 100%;
            border-radius: 0;
        }
    }

    @media (max-height: 350px) {
        h1 {
            margin: 0 0 15px;
        }

        .icon-offline {
            margin: 0 0 10px;
        }

        .interstitial-wrapper {
            margin-top: 5%;
        }

        .nav-wrapper {
            margin-top: 30px;
        }
    }

    @media (min-width: 420px) and (max-width: 736px) and
    (min-height: 240px) and (max-height: 420px) and
    (orientation:landscape) {
        .interstitial-wrapper {
            margin-bottom: 100px;
        }
    }

    @media (max-width: 360px) and (max-height: 480px) {
        .offline .interstitial-wrapper {
            padding-top: 60px;
        }

        .offline .runner-container {
            top: 8px;
        }
    }

    @media (min-height: 240px) and (orientation: landscape) {
        .offline .interstitial-wrapper {
            margin-bottom: 90px;
        }

        .icon-offline {
            margin-bottom: 20px;
        }
    }

    @media (max-height: 320px) and (orientation: landscape) {
        .icon-offline {
            margin-bottom: 0;
        }

        .offline .runner-container {
            top: 10px;
        }
    }

    @media (max-width: 240px) {
        button {
            padding-left: 12px;
            padding-right: 12px;
        }

        .interstitial-wrapper {
            overflow: inherit;
            padding: 0 8px;
        }
    }

    @media (max-width: 120px) {
        button {
            width: auto;
        }
    }

    .arcade-mode,
    .arcade-mode .runner-container,
    .arcade-mode .runner-canvas {
        image-rendering: pixelated;
        max-width: 100%;
        overflow: hidden;
    }

    .arcade-mode #buttons,
    .arcade-mode #main-content {
        opacity: 0;
        overflow: hidden;
    }

    .arcade-mode .interstitial-wrapper {
        height: 100vh;
        max-width: 100%;
        overflow: hidden;
    }

    .arcade-mode .runner-container {
        left: 0;
        margin: auto;
        right: 0;
        transform-origin: top center;
        transition: transform 250ms cubic-bezier(0.4, 0.0, 1, 1) .4s;
        z-index: 2;
    }

    @media (prefers-color-scheme: dark) {
        .icon {
            filter: invert(1);
        }

        .offline .runner-canvas {
            filter: invert(1);
        }

        .offline.inverted {
            filter: invert(0);

            background-color: var(--background-color);


        }

        .offline.inverted body {
            background-color: #fff;
        }

        #suggestions-list a {
            color: var(--link-color);
        }

        #error-information-button {
            filter: invert(0.6);
        }
    }
    </style>
    <script>// Copyright 2017 The Chromium Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.

    // This is the shared code for security interstitials. It is used for both SSL
    // interstitials and Safe Browsing interstitials.

    /**
     * @typedef {{
     *   dontProceed: function(),
     *   proceed: function(),
     *   showMoreSection: function(),
     *   openHelpCenter: function(),
     *   openDiagnostic: function(),
     *   reload: function(),
     *   openDateSettings: function(),
     *   openLogin: function(),
     *   doReport: function(),
     *   dontReport: function(),
     *   openReportingPrivacy: function(),
     *   openWhitepaper: function(),
     *   reportPhishingError: function(),
     * }}
     */
// eslint-disable-next-line no-var
    var certificateErrorPageController;

    // Should match security_interstitials::SecurityInterstitialCommand
    /** @enum {number} */
    const SecurityInterstitialCommandId = {
        CMD_DONT_PROCEED: 0,
        CMD_PROCEED: 1,
        // Ways for user to get more information
        CMD_SHOW_MORE_SECTION: 2,
        CMD_OPEN_HELP_CENTER: 3,
        CMD_OPEN_DIAGNOSTIC: 4,
        // Primary button actions
        CMD_RELOAD: 5,
        CMD_OPEN_DATE_SETTINGS: 6,
        CMD_OPEN_LOGIN: 7,
        // Safe Browsing Extended Reporting
        CMD_DO_REPORT: 8,
        CMD_DONT_REPORT: 9,
        CMD_OPEN_REPORTING_PRIVACY: 10,
        CMD_OPEN_WHITEPAPER: 11,
        // Report a phishing error.
        CMD_REPORT_PHISHING_ERROR: 12
    };

    const HIDDEN_CLASS = 'hidden';

    /**
     * A convenience method for sending commands to the parent page.
     * @param {SecurityInterstitialCommandId} cmd  The command to send.
     */
    function sendCommand(cmd) {
        if (window.certificateErrorPageController) {
            switch (cmd) {
                case SecurityInterstitialCommandId.CMD_DONT_PROCEED:
                    certificateErrorPageController.dontProceed();
                    break;
                case SecurityInterstitialCommandId.CMD_PROCEED:
                    certificateErrorPageController.proceed();
                    break;
                case SecurityInterstitialCommandId.CMD_SHOW_MORE_SECTION:
                    certificateErrorPageController.showMoreSection();
                    break;
                case SecurityInterstitialCommandId.CMD_OPEN_HELP_CENTER:
                    certificateErrorPageController.openHelpCenter();
                    break;
                case SecurityInterstitialCommandId.CMD_OPEN_DIAGNOSTIC:
                    certificateErrorPageController.openDiagnostic();
                    break;
                case SecurityInterstitialCommandId.CMD_RELOAD:
                    certificateErrorPageController.reload();
                    break;
                case SecurityInterstitialCommandId.CMD_OPEN_DATE_SETTINGS:
                    certificateErrorPageController.openDateSettings();
                    break;
                case SecurityInterstitialCommandId.CMD_OPEN_LOGIN:
                    certificateErrorPageController.openLogin();
                    break;
                case SecurityInterstitialCommandId.CMD_DO_REPORT:
                    certificateErrorPageController.doReport();
                    break;
                case SecurityInterstitialCommandId.CMD_DONT_REPORT:
                    certificateErrorPageController.dontReport();
                    break;
                case SecurityInterstitialCommandId.CMD_OPEN_REPORTING_PRIVACY:
                    certificateErrorPageController.openReportingPrivacy();
                    break;
                case SecurityInterstitialCommandId.CMD_OPEN_WHITEPAPER:
                    certificateErrorPageController.openWhitepaper();
                    break;
                case SecurityInterstitialCommandId.CMD_REPORT_PHISHING_ERROR:
                    certificateErrorPageController.reportPhishingError();
                    break;
            }
            return;
        }
        //
        window.domAutomationController.send(cmd);
        //
        //
    }

    /**
     * Call this to stop clicks on <a href="#"> links from scrolling to the top of
     * the page (and possibly showing a # in the link).
     */
    function preventDefaultOnPoundLinkClicks() {
        document.addEventListener('click', function(e) {
            const anchor = findAncestor(/** @type {Node} */ (e.target), function(el) {
                return el.tagName === 'A';
            });
            // Use getAttribute() to prevent URL normalization.
            if (anchor && anchor.getAttribute('href') === '#') {
                e.preventDefault();
            }
        });
    }

    //

    //
    </script>
    <script>// Copyright 2015 The Chromium Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.

    let mobileNav = false;

    /**
     * For small screen mobile the navigation buttons are moved
     * below the advanced text.
     */
    function onResize() {
        const helpOuterBox = document.querySelector('#details');
        const mainContent = document.querySelector('#main-content');
        const mediaQuery = '(min-width: 240px) and (max-width: 420px) and ' +
            '(min-height: 401px), ' +
            '(max-height: 560px) and (min-height: 240px) and ' +
            '(min-width: 421px)';

        const detailsHidden = helpOuterBox.classList.contains(HIDDEN_CLASS);
        const runnerContainer = document.querySelector('.runner-container');

        // Check for change in nav status.
        if (mobileNav !== window.matchMedia(mediaQuery).matches) {
            mobileNav = !mobileNav;

            // Handle showing the top content / details sections according to state.
            if (mobileNav) {
                mainContent.classList.toggle(HIDDEN_CLASS, !detailsHidden);
                helpOuterBox.classList.toggle(HIDDEN_CLASS, detailsHidden);
                if (runnerContainer) {
                    runnerContainer.classList.toggle(HIDDEN_CLASS, !detailsHidden);
                }
            } else if (!detailsHidden) {
                // Non mobile nav with visible details.
                mainContent.classList.remove(HIDDEN_CLASS);
                helpOuterBox.classList.remove(HIDDEN_CLASS);
                if (runnerContainer) {
                    runnerContainer.classList.remove(HIDDEN_CLASS);
                }
            }
        }
    }

    function setupMobileNav() {
        window.addEventListener('resize', onResize);
        onResize();
    }

    document.addEventListener('DOMContentLoaded', setupMobileNav);
    </script>
    <script>// Copyright 2013 The Chromium Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.

    /**
     * @typedef {{
     *   downloadButtonClick: function(),
     *   reloadButtonClick: function(),
     *   detailsButtonClick: function(),
     *   diagnoseErrorsButtonClick: function(),
     *   trackClick: function(number),
     *   trackEasterEgg: function(),
     *   updateEasterEggHighScore: function(number),
     *   resetEasterEggHighScore: function(),
     *   trackCachedCopyButtonClick: function(),
     *   launchOfflineItem: function(string, string),
     *   savePageForLater: function(),
     *   cancelSavePage: function(),
     *   listVisibilityChange: function(boolean),
     * }}
     */
// eslint-disable-next-line no-var
    var errorPageController;

    // Decodes a UTF16 string that is encoded as base64.
    function decodeUTF16Base64ToString(encoded_text) {
        const data = atob(encoded_text);
        let result = '';
        for (let i = 0; i < data.length; i += 2) {
            result +=
                String.fromCharCode(data.charCodeAt(i) * 256 + data.charCodeAt(i + 1));
        }
        return result;
    }

    function toggleHelpBox() {
        const helpBoxOuter = document.getElementById('details');
        helpBoxOuter.classList.toggle(HIDDEN_CLASS);
        const detailsButton = document.getElementById('details-button');
        if (helpBoxOuter.classList.contains(HIDDEN_CLASS)) {
            /** @suppress {missingProperties} */
            detailsButton.innerText = detailsButton.detailsText;
        } else {
            /** @suppress {missingProperties} */
            detailsButton.innerText = detailsButton.hideDetailsText;
        }

        // Details appears over the main content on small screens.
        if (mobileNav) {
            document.getElementById('main-content').classList.toggle(HIDDEN_CLASS);
            const runnerContainer = document.querySelector('.runner-container');
            if (runnerContainer) {
                runnerContainer.classList.toggle(HIDDEN_CLASS);
            }
        }
    }

    function diagnoseErrors() {
//
        if (window.errorPageController) {
            errorPageController.diagnoseErrorsButtonClick();
        }
//
//
    }

    // Subframes use a different layout but the same html file.  This is to make it
    // easier to support platforms that load the error page via different
    // mechanisms (Currently just iOS). We also use the subframe style for portals
    // as they are embedded like subframes and can't be interacted with by the user.
    if (window.top.location !== window.location || window.portalHost) {
        document.documentElement.setAttribute('subframe', '');
    }

    // Re-renders the error page using |strings| as the dictionary of values.
    // Used by NetErrorTabHelper to update DNS error pages with probe results.
    function updateForDnsProbe(strings) {
        const context = new JsEvalContext(strings);
        jstProcess(context, document.getElementById('t'));
        onDocumentLoadOrUpdate();
    }

    // Given the classList property of an element, adds an icon class to the list
    // and removes the previously-
    function updateIconClass(classList, newClass) {
        let oldClass;

        if (classList.hasOwnProperty('last_icon_class')) {
            oldClass = classList['last_icon_class'];
            if (oldClass === newClass) {
                return;
            }
        }

        classList.add(newClass);
        if (oldClass !== undefined) {
            classList.remove(oldClass);
        }

        classList['last_icon_class'] = newClass;

        if (newClass === 'icon-offline') {
            document.firstElementChild.classList.add('offline');
            new Runner('.interstitial-wrapper');
        } else {
            document.body.classList.add('neterror');
        }
    }

    // Does a search using |baseSearchUrl| and the text in the search box.
    function search(baseSearchUrl) {
        const searchTextNode = document.getElementById('search-box');
        document.location = baseSearchUrl + searchTextNode.value;
        return false;
    }

    // Use to track clicks on elements generated by the navigation correction
    // service.  If |trackingId| is negative, the element does not come from the
    // correction service.
    function trackClick(trackingId) {
        // This can't be done with XHRs because XHRs are cancelled on navigation
        // start, and because these are cross-site requests.
        if (trackingId >= 0 && errorPageController) {
            errorPageController.trackClick(trackingId);
        }
    }

    // Called when an <a> tag generated by the navigation correction service is
    // clicked.  Separate function from trackClick so the resources don't have to
    // be updated if new data is added to jstdata.
    // @param {{trackingId: number}} jstdata
    function linkClicked(jstdata) {
        trackClick(jstdata.trackingId);
    }

    // Implements button clicks.  This function is needed during the transition
    // between implementing these in trunk chromium and implementing them in
    // iOS.
    function reloadButtonClick(url) {
        /*if (window.errorPageController) {
                    errorPageController.reloadButtonClick();
                } else {
                    window.location = url;
                }*/
    }

    function downloadButtonClick() {
        if (window.errorPageController) {
            errorPageController.downloadButtonClick();
            const downloadButton = document.getElementById('download-button');
            downloadButton.disabled = true;
            /** @suppress {missingProperties} */
            downloadButton.textContent = downloadButton.disabledText;

            document.getElementById('download-link-wrapper')
                .classList.add(HIDDEN_CLASS);
            document.getElementById('download-link-clicked-wrapper')
                .classList.remove(HIDDEN_CLASS);
        }
    }

    function detailsButtonClick() {
        if (window.errorPageController) {
            errorPageController.detailsButtonClick();
        }
    }

    /**
     * @typedef {{
     *   msg: string,
     *   cacheUrl: string,
     *   trackingId: number
     * }}
     */
    let CacheButtonParams;

    /**
     * Replace the reload button with the Google cached copy suggestion.
     * @param {CacheButtonParams} buttonStrings
     */
    function setUpCachedButton(buttonStrings) {
        const reloadButton = document.getElementById('reload-button');

        reloadButton.textContent = buttonStrings.msg;
        const url = buttonStrings.cacheUrl;
        const trackingId = buttonStrings.trackingId;
        reloadButton.onclick = function(e) {
            e.preventDefault();
            trackClick(trackingId);
            if (window.errorPageController) {
                errorPageController.trackCachedCopyButtonClick();
            }
            window.location = url;
        };
        reloadButton.style.display = '';
    }

    let primaryControlOnLeft = true;
    //
    primaryControlOnLeft = false;
    //

    function setAutoFetchState(scheduled, can_schedule) {
        document.getElementById('cancel-save-page-button')
            .classList.toggle(HIDDEN_CLASS, !scheduled);
        document.getElementById('save-page-for-later-button')
            .classList.toggle(HIDDEN_CLASS, scheduled || !can_schedule);
    }

    function savePageLaterClick() {
        errorPageController.savePageForLater();
        // savePageForLater will eventually trigger a call to setAutoFetchState() when
        // it completes.
    }

    function cancelSavePageClick() {
        errorPageController.cancelSavePage();
        // setAutoFetchState is not called in response to cancelSavePage(), so do it
        // now.
        setAutoFetchState(false, true);
    }

    function toggleErrorInformationPopup() {
        document.getElementById('error-information-popup-container')
            .classList.toggle(HIDDEN_CLASS);
    }

    function launchOfflineItem(itemID, name_space) {
        errorPageController.launchOfflineItem(itemID, name_space);
    }

    function launchDownloadsPage() {
        errorPageController.launchDownloadsPage();
    }

    function getIconForSuggestedItem(item) {
        // Note: |item.content_type| contains the enum values from
        // browser::mojom::AvailableContentType.
        switch (item.content_type) {
            case 1:  // kVideo
                return 'image-video';
            case 2:  // kAudio
                return 'image-music-note';
            case 0:  // kPrefetchedPage
            case 3:  // kOtherPage
                return 'image-earth';
        }
        return 'image-file';
    }


    /**
     * @typedef {{
     *   ID: string,
     *   name_space: string,
     *   title_base64: string,
     *   snippet_base64: string,
     *   date_modified: string,
     *   attribution_base64: string,
     *   thumbnail_data_uri: string,
     *   favicon_data_uri: string,
     *   content_type: number,
     * }}
     */
    let AvailableOfflineContent;

    // Populates a list of suggested offline content.
    // Note: For security reasons all content downloaded from the web is considered
    // unsafe and must be securely handled to be presented on the dino page. Images
    // have already been safely re-encoded but textual content -- like title and
    // attribution -- must be properly handled here.
    // @param {boolean} isShown
    // @param {Array<AvailableOfflineContent>} suggestions

    function toggleOfflineContentListVisibility(updatePref) {
        if (!loadTimeData.valueExists('offlineContentList')) {
            return;
        }

        const contentListElement = document.getElementById('offline-content-list');
        const isVisible = !contentListElement.classList.toggle('list-hidden');

        if (updatePref && window.errorPageController) {
            errorPageController.listVisibilityChanged(isVisible);
        }
    }

    // Called on document load, and from updateForDnsProbe().
    function onDocumentLoadOrUpdate() {
        const downloadButtonVisible = loadTimeData.valueExists('downloadButton') &&
            loadTimeData.getValue('downloadButton').msg;
        const detailsButton = document.getElementById('details-button');

        // If offline content suggestions will be visible, the usual buttons will not
        // be presented.
        const offlineContentVisible =
            loadTimeData.valueExists('suggestedOfflineContentPresentation');
        if (offlineContentVisible) {
            document.querySelector('.nav-wrapper').classList.add(HIDDEN_CLASS);
            detailsButton.classList.add(HIDDEN_CLASS);

            document.getElementById('download-link').hidden = !downloadButtonVisible;
            document.getElementById('download-links-wrapper')
                .classList.remove(HIDDEN_CLASS);
            document.getElementById('error-information-popup-container')
                .classList.add('use-popup-container', HIDDEN_CLASS);
            document.getElementById('error-information-button')
                .classList.remove(HIDDEN_CLASS);
        }

        const attemptAutoFetch = loadTimeData.valueExists('attemptAutoFetch') &&
            loadTimeData.getValue('attemptAutoFetch');

        const reloadButtonVisible = loadTimeData.valueExists('reloadButton') &&
            loadTimeData.getValue('reloadButton').msg;

        // Check for Google cached copy suggestion.
        let cacheButtonVisible = false;
        if (loadTimeData.valueExists('cacheButton')) {
            setUpCachedButton(/** @type {CacheButtonParams} */
                (loadTimeData.getValue('cacheButton')));
            cacheButtonVisible = true;
        }

        const reloadButton = document.getElementById('reload-button');
        const downloadButton = document.getElementById('download-button');
        if (reloadButton.style.display === 'none' &&
            downloadButton.style.display === 'none') {
            detailsButton.classList.add('singular');
        }

        // Show or hide control buttons.
        const controlButtonDiv = document.getElementById('control-buttons');
        controlButtonDiv.hidden = offlineContentVisible ||
            !(reloadButtonVisible || downloadButtonVisible || cacheButtonVisible);
    }

    function onDocumentLoad() {
        // Sets up the proper button layout for the current platform.
        const buttonsDiv = document.getElementById('buttons');
        if (primaryControlOnLeft) {
            buttonsDiv.classList.add('suggested-left');
        } else {
            buttonsDiv.classList.add('suggested-right');
        }

        onDocumentLoadOrUpdate();
    }

    document.addEventListener('DOMContentLoaded', onDocumentLoad);
    </script>
    <script>// Copyright (c) 2014 The Chromium Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.

    /**
     * T-Rex runner.
     * @param {string} outerContainerId Outer containing element id.
     * @param {!Object=} opt_config
     * @constructor
     * @implements {EventListener}
     * @export
     */
    function Runner(outerContainerId, opt_config) {
        // Singleton
        if (Runner.instance_) {
            return Runner.instance_;
        }
        Runner.instance_ = this;

        this.outerContainerEl = document.querySelector(outerContainerId);
        this.containerEl = null;
        this.snackbarEl = null;
        // A div to intercept touch events. Only set while (playing && useTouch).
        this.touchController = null;

        this.config = opt_config || Runner.config;
        // Logical dimensions of the container.
        this.dimensions = Runner.defaultDimensions;

        this.canvas = null;
        this.canvasCtx = null;

        this.tRex = null;

        this.distanceMeter = null;
        this.distanceRan = 0;

        this.highestScore = 0;
        this.syncHighestScore = false;

        this.time = 0;
        this.runningTime = 0;
        this.msPerFrame = 1000 / FPS;
        this.currentSpeed = this.config.SPEED;

        this.obstacles = [];

        this.activated = false; // Whether the easter egg has been activated.
        this.playing = false; // Whether the game is currently in play state.
        this.crashed = false;
        this.paused = false;
        this.inverted = false;
        this.invertTimer = 0;
        this.resizeTimerId_ = null;

        this.playCount = 0;

        // Sound FX.
        this.audioBuffer = null;
        /** @type {Object} */
        this.soundFx = {};

        // Global web audio context for playing sounds.
        this.audioContext = null;

        // Images.
        this.images = {};
        this.imagesLoaded = 0;

        // Gamepad state.
        this.pollingGamepads = false;
        this.gamepadIndex = undefined;
        this.previousGamepad = null;

        if (this.isDisabled()) {
            this.setupDisabledRunner();
        } else {
            this.loadImages();

            window['initializeEasterEggHighScore'] =
                this.initializeHighScore.bind(this);
        }
    }

    /**
     * Default game width.
     * @const
     */
    const DEFAULT_WIDTH = 600;

    /**
     * Frames per second.
     * @const
     */
    const FPS = 60;

    /** @const */
    const IS_HIDPI = window.devicePixelRatio > 1;

    /** @const */
    const IS_IOS = /CriOS/.test(window.navigator.userAgent);

    /** @const */
    const IS_MOBILE = /Android/.test(window.navigator.userAgent) || IS_IOS;

    /** @const */
    const ARCADE_MODE_URL = 'browser://dino/';

    /**
     * Default game configuration.
     */
    Runner.config = {
        ACCELERATION: 0.001,
        BG_CLOUD_SPEED: 0.2,
        BOTTOM_PAD: 10,
        // Scroll Y threshold at which the game can be activated.
        CANVAS_IN_VIEW_OFFSET: -10,
        CLEAR_TIME: 3000,
        CLOUD_FREQUENCY: 0.5,
        GAMEOVER_CLEAR_TIME: 750,
        GAP_COEFFICIENT: 0.6,
        GRAVITY: 0.6,
        INITIAL_JUMP_VELOCITY: 12,
        INVERT_FADE_DURATION: 12000,
        INVERT_DISTANCE: 700,
        MAX_BLINK_COUNT: 3,
        MAX_CLOUDS: 6,
        MAX_OBSTACLE_LENGTH: 3,
        MAX_OBSTACLE_DUPLICATION: 2,
        MAX_SPEED: 13,
        MIN_JUMP_HEIGHT: 35,
        MOBILE_SPEED_COEFFICIENT: 1.2,
        RESOURCE_TEMPLATE_ID: 'audio-resources',
        SPEED: 6,
        SPEED_DROP_COEFFICIENT: 3,
        ARCADE_MODE_INITIAL_TOP_POSITION: 35,
        ARCADE_MODE_TOP_POSITION_PERCENT: 0.1
    };


    /**
     * Default dimensions.
     */
    Runner.defaultDimensions = {
        WIDTH: DEFAULT_WIDTH,
        HEIGHT: 150
    };


    /**
     * CSS class names.
     * @enum {string}
     */
    Runner.classes = {
        ARCADE_MODE: 'arcade-mode',
        CANVAS: 'runner-canvas',
        CONTAINER: 'runner-container',
        CRASHED: 'crashed',
        ICON: 'icon-offline',
        INVERTED: 'inverted',
        SNACKBAR: 'snackbar',
        SNACKBAR_SHOW: 'snackbar-show',
        TOUCH_CONTROLLER: 'controller'
    };


    /**
     * Sprite definition layout of the spritesheet.
     * @enum {Object}
     */
    Runner.spriteDefinition = {
        LDPI: {
            CACTUS_LARGE: {x: 332, y: 2},
            CACTUS_SMALL: {x: 228, y: 2},
            CLOUD: {x: 86, y: 2},
            HORIZON: {x: 2, y: 54},
            MOON: {x: 484, y: 2},
            PTERODACTYL: {x: 134, y: 2},
            RESTART: {x: 2, y: 2},
            TEXT_SPRITE: {x: 655, y: 2},
            TREX: {x: 848, y: 2},
            STAR: {x: 645, y: 2}
        },
        HDPI: {
            CACTUS_LARGE: {x: 652, y: 2},
            CACTUS_SMALL: {x: 446, y: 2},
            CLOUD: {x: 166, y: 2},
            HORIZON: {x: 2, y: 104},
            MOON: {x: 954, y: 2},
            PTERODACTYL: {x: 260, y: 2},
            RESTART: {x: 2, y: 2},
            TEXT_SPRITE: {x: 1294, y: 2},
            TREX: {x: 1678, y: 2},
            STAR: {x: 1276, y: 2}
        }
    };


    /**
     * Sound FX. Reference to the ID of the audio tag on interstitial page.
     * @enum {string}
     */
    Runner.sounds = {
        BUTTON_PRESS: 'offline-sound-press',
        HIT: 'offline-sound-hit',
        SCORE: 'offline-sound-reached'
    };


    /**
     * Key code mapping.
     * @enum {Object}
     */
    Runner.keycodes = {
        JUMP: {'38': 1, '32': 1},  // Up, spacebar
        DUCK: {'40': 1},  // Down
        RESTART: {'13': 1}  // Enter
    };


    /**
     * Runner event names.
     * @enum {string}
     */
    Runner.events = {
        ANIM_END: 'webkitAnimationEnd',
        CLICK: 'click',
        KEYDOWN: 'keydown',
        KEYUP: 'keyup',
        POINTERDOWN: 'pointerdown',
        POINTERUP: 'pointerup',
        RESIZE: 'resize',
        TOUCHEND: 'touchend',
        TOUCHSTART: 'touchstart',
        VISIBILITY: 'visibilitychange',
        BLUR: 'blur',
        FOCUS: 'focus',
        LOAD: 'load',
        GAMEPADCONNECTED: 'gamepadconnected',
    };

    Runner.prototype = {
        /**
         * Whether the easter egg has been disabled. CrOS enterprise enrolled devices.
         * @return {boolean}
         */
        isDisabled() {
            return loadTimeData && loadTimeData.valueExists('disabledEasterEgg');
        },

        /**
         * For disabled instances, set up a snackbar with the disabled message.
         */
        setupDisabledRunner() {
            this.containerEl = document.createElement('div');
            this.containerEl.className = Runner.classes.SNACKBAR;
            this.containerEl.textContent = loadTimeData.getValue('disabledEasterEgg');
            this.outerContainerEl.appendChild(this.containerEl);

            // Show notification when the activation key is pressed.
            document.addEventListener(Runner.events.KEYDOWN, function(e) {
                if (Runner.keycodes.JUMP[e.keyCode]) {
                    this.containerEl.classList.add(Runner.classes.SNACKBAR_SHOW);
                    document.querySelector('.icon').classList.add('icon-disabled');
                }
            }.bind(this));
        },

        /**
         * Setting individual settings for debugging.
         * @param {string} setting
         * @param {number|string} value
         */
        updateConfigSetting(setting, value) {
            if (setting in this.config && value !== undefined) {
                this.config[setting] = value;

                switch (setting) {
                    case 'GRAVITY':
                    case 'MIN_JUMP_HEIGHT':
                    case 'SPEED_DROP_COEFFICIENT':
                        this.tRex.config[setting] = value;
                        break;
                    case 'INITIAL_JUMP_VELOCITY':
                        this.tRex.setJumpVelocity(value);
                        break;
                    case 'SPEED':
                        this.setSpeed(/** @type {number} */ (value));
                        break;
                }
            }
        },

        /**
         * Cache the appropriate image sprite from the page and get the sprite sheet
         * definition.
         */
        loadImages() {
            if (IS_HIDPI) {
                Runner.imageSprite = /** @type {HTMLImageElement} */
                    (document.getElementById('offline-resources-2x'));
                this.spriteDef = Runner.spriteDefinition.HDPI;
            } else {
                Runner.imageSprite = /** @type {HTMLImageElement} */
                    (document.getElementById('offline-resources-1x'));
                this.spriteDef = Runner.spriteDefinition.LDPI;
            }

            if (Runner.imageSprite.complete) {
                this.init();
            } else {
                // If the images are not yet loaded, add a listener.
                Runner.imageSprite.addEventListener(Runner.events.LOAD,
                    this.init.bind(this));
            }
        },

        /**
         * Load and decode base 64 encoded sounds.
         */
        loadSounds() {
            if (!IS_IOS) {
                this.audioContext = new AudioContext();

                const resourceTemplate =
                    document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content;

                for (const sound in Runner.sounds) {
                    let soundSrc =
                        resourceTemplate.getElementById(Runner.sounds[sound]).src;
                    soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);
                    const buffer = decodeBase64ToArrayBuffer(soundSrc);

                    // Async, so no guarantee of order in array.
                    this.audioContext.decodeAudioData(buffer, function(index, audioData) {
                        this.soundFx[index] = audioData;
                    }.bind(this, sound));
                }
            }
        },

        /**
         * Sets the game speed. Adjust the speed accordingly if on a smaller screen.
         * @param {number=} opt_speed
         */
        setSpeed(opt_speed) {
            const speed = opt_speed || this.currentSpeed;

            // Reduce the speed on smaller mobile screens.
            if (this.dimensions.WIDTH < DEFAULT_WIDTH) {
                const mobileSpeed = speed * this.dimensions.WIDTH / DEFAULT_WIDTH *
                    this.config.MOBILE_SPEED_COEFFICIENT;
                this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed;
            } else if (opt_speed) {
                this.currentSpeed = opt_speed;
            }
        },

        /**
         * Game initialiser.
         */
        init() {
            // Hide the static icon.
            document.querySelector('.' + Runner.classes.ICON).style.visibility =
                'hidden';

            this.adjustDimensions();
            this.setSpeed();

            this.containerEl = document.createElement('div');
            this.containerEl.className = Runner.classes.CONTAINER;

            // Player canvas container.
            this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH,
                this.dimensions.HEIGHT);

            this.canvasCtx =
                /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d'));
            this.canvasCtx.fillStyle = '#f7f7f7';
            this.canvasCtx.fill();
            Runner.updateCanvasScaling(this.canvas);

            // Horizon contains clouds, obstacles and the ground.
            this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions,
                this.config.GAP_COEFFICIENT);

            // Distance meter
            this.distanceMeter = new DistanceMeter(this.canvas,
                this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH);

            // Draw t-rex
            this.tRex = new Trex(this.canvas, this.spriteDef.TREX);

            this.outerContainerEl.appendChild(this.containerEl);

            this.startListening();
            this.update();

            window.addEventListener(Runner.events.RESIZE,
                this.debounceResize.bind(this));

            // Handle dark mode
            const darkModeMediaQuery =
                window.matchMedia('(prefers-color-scheme: dark)');
            this.isDarkMode = darkModeMediaQuery && darkModeMediaQuery.matches;
            darkModeMediaQuery.addListener((e) => {
                this.isDarkMode = e.matches;
            });
        },

        /**
         * Create the touch controller. A div that covers whole screen.
         */
        createTouchController() {
            this.touchController = document.createElement('div');
            this.touchController.className = Runner.classes.TOUCH_CONTROLLER;
            this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
            this.touchController.addEventListener(Runner.events.TOUCHEND, this);
            this.outerContainerEl.appendChild(this.touchController);
        },

        /**
         * Debounce the resize event.
         */
        debounceResize() {
            if (!this.resizeTimerId_) {
                this.resizeTimerId_ =
                    setInterval(this.adjustDimensions.bind(this), 250);
            }
        },

        /**
         * Adjust game space dimensions on resize.
         */
        adjustDimensions() {
            clearInterval(this.resizeTimerId_);
            this.resizeTimerId_ = null;

            const boxStyles = window.getComputedStyle(this.outerContainerEl);
            const padding = Number(boxStyles.paddingLeft.substr(0,
                boxStyles.paddingLeft.length - 2));

            this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2;
            if (this.isArcadeMode()) {
                this.dimensions.WIDTH = Math.min(DEFAULT_WIDTH, this.dimensions.WIDTH);
                if (this.activated) {
                    this.setArcadeModeContainerScale();
                }
            }

            // Redraw the elements back onto the canvas.
            if (this.canvas) {
                this.canvas.width = this.dimensions.WIDTH;
                this.canvas.height = this.dimensions.HEIGHT;

                Runner.updateCanvasScaling(this.canvas);

                this.distanceMeter.calcXPos(this.dimensions.WIDTH);
                this.clearCanvas();
                this.horizon.update(0, 0, true);
                this.tRex.update(0);

                // Outer container and distance meter.
                if (this.playing || this.crashed || this.paused) {
                    this.containerEl.style.width = this.dimensions.WIDTH + 'px';
                    this.containerEl.style.height = this.dimensions.HEIGHT + 'px';
                    this.distanceMeter.update(0, Math.ceil(this.distanceRan));
                    this.stop();
                } else {
                    this.tRex.draw(0, 0);
                }

                // Game over panel.
                if (this.crashed && this.gameOverPanel) {
                    this.gameOverPanel.updateDimensions(this.dimensions.WIDTH);
                    this.gameOverPanel.draw();
                }
            }
        },

        /**
         * Play the game intro.
         * Canvas container width expands out to the full width.
         */
        playIntro() {
            if (!this.activated && !this.crashed) {
                this.playingIntro = true;
                this.tRex.playingIntro = true;

                // CSS animation definition.
                const keyframes = '@-webkit-keyframes intro { ' +
                    'from { width:' + Trex.config.WIDTH + 'px }' +
                    'to { width: ' + this.dimensions.WIDTH + 'px }' +
                    '}';
                document.styleSheets[0].insertRule(keyframes, 0);

                this.containerEl.addEventListener(Runner.events.ANIM_END,
                    this.startGame.bind(this));

                this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
                this.containerEl.style.width = this.dimensions.WIDTH + 'px';

                this.setPlayStatus(true);
                this.activated = true;
            } else if (this.crashed) {
                this.restart();
            }
        },


        /**
         * Update the game status to started.
         */
        startGame() {
            if (this.isArcadeMode()) {
                this.setArcadeMode();
            }
            this.runningTime = 0;
            this.playingIntro = false;
            this.tRex.playingIntro = false;
            this.containerEl.style.webkitAnimation = '';
            this.playCount++;

            // Handle tabbing off the page. Pause the current game.
            document.addEventListener(Runner.events.VISIBILITY,
                this.onVisibilityChange.bind(this));

            window.addEventListener(Runner.events.BLUR,
                this.onVisibilityChange.bind(this));

            window.addEventListener(Runner.events.FOCUS,
                this.onVisibilityChange.bind(this));
        },

        clearCanvas() {
            this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH,
                this.dimensions.HEIGHT);
        },

        /**
         * Checks whether the canvas area is in the viewport of the browser
         * through the current scroll position.
         * @return boolean.
         */
        isCanvasInView() {
            return this.containerEl.getBoundingClientRect().top >
                Runner.config.CANVAS_IN_VIEW_OFFSET;
        },

        /**
         * Update the game frame and schedules the next one.
         */
        update() {
            this.updatePending = false;

            const now = getTimeStamp();
            let deltaTime = now - (this.time || now);

            this.time = now;

            if (this.playing) {
                this.clearCanvas();

                if (this.tRex.jumping) {
                    this.tRex.updateJump(deltaTime);
                }

                this.runningTime += deltaTime;
                const hasObstacles = this.runningTime > this.config.CLEAR_TIME;

                // First jump triggers the intro.
                if (this.tRex.jumpCount === 1 && !this.playingIntro) {
                    this.playIntro();
                }

                // The horizon doesn't move until the intro is over.
                if (this.playingIntro) {
                    this.horizon.update(0, this.currentSpeed, hasObstacles);
                } else {
                    const showNightMode = this.isDarkMode ^ this.inverted;
                    deltaTime = !this.activated ? 0 : deltaTime;
                    this.horizon.update(
                        deltaTime, this.currentSpeed, hasObstacles, showNightMode);
                }

                // Check for collisions.
                const collision = hasObstacles &&
                    checkForCollision(this.horizon.obstacles[0], this.tRex);

                if (!collision) {
                    this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;

                    if (this.currentSpeed < this.config.MAX_SPEED) {
                        this.currentSpeed += this.config.ACCELERATION;
                    }
                } else {
                    this.gameOver();
                }

                const playAchievementSound = this.distanceMeter.update(deltaTime,
                    Math.ceil(this.distanceRan));

                if (playAchievementSound) {
                    this.playSound(this.soundFx.SCORE);
                }

                // Night mode.
                if (this.invertTimer > this.config.INVERT_FADE_DURATION) {
                    this.invertTimer = 0;
                    this.invertTrigger = false;
                    this.invert(false);
                } else if (this.invertTimer) {
                    this.invertTimer += deltaTime;
                } else {
                    const actualDistance =
                        this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan));

                    if (actualDistance > 0) {
                        this.invertTrigger = !(actualDistance %
                            this.config.INVERT_DISTANCE);

                        if (this.invertTrigger && this.invertTimer === 0) {
                            this.invertTimer += deltaTime;
                            this.invert(false);
                        }
                    }
                }
            }

            if (this.playing || (!this.activated &&
                this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) {
                this.tRex.update(deltaTime);
                this.scheduleNextUpdate();
            }
        },

        /**
         * Event handler.
         * @param {Event} e
         */
        handleEvent(e) {
            return (function(evtType, events) {
                switch (evtType) {
                    case events.KEYDOWN:
                    case events.TOUCHSTART:
                    case events.POINTERDOWN:
                        this.onKeyDown(e);
                        break;
                    case events.KEYUP:
                    case events.TOUCHEND:
                    case events.POINTERUP:
                        this.onKeyUp(e);
                        break;
                    case events.GAMEPADCONNECTED:
                        this.onGamepadConnected(e);
                        break;
                }
            }.bind(this))(e.type, Runner.events);
        },

        /**
         * Bind relevant key / mouse / touch listeners.
         */
        startListening() {
            // Keys.
            document.addEventListener(Runner.events.KEYDOWN, this);
            document.addEventListener(Runner.events.KEYUP, this);

            // Touch / pointer.
            this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);
            document.addEventListener(Runner.events.POINTERDOWN, this);
            document.addEventListener(Runner.events.POINTERUP, this);

            if (this.isArcadeMode()) {
                // Gamepad
                window.addEventListener(Runner.events.GAMEPADCONNECTED, this);
            }
        },

        /**
         * Remove all listeners.
         */
        stopListening() {
            document.removeEventListener(Runner.events.KEYDOWN, this);
            document.removeEventListener(Runner.events.KEYUP, this);

            if (this.touchController) {
                this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);
                this.touchController.removeEventListener(Runner.events.TOUCHEND, this);
            }

            this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);
            document.removeEventListener(Runner.events.POINTERDOWN, this);
            document.removeEventListener(Runner.events.POINTERUP, this);

            if (this.isArcadeMode()) {
                window.removeEventListener(Runner.events.GAMEPADCONNECTED, this);
            }
        },

        /**
         * Process keydown.
         * @param {Event} e
         */
        onKeyDown(e) {
            // Prevent native page scrolling whilst tapping on mobile.
            if (IS_MOBILE && this.playing) {
                e.preventDefault();
            }

            if (this.isCanvasInView()) {
                if (!this.crashed && !this.paused) {
                    if (Runner.keycodes.JUMP[e.keyCode] ||
                        e.type === Runner.events.TOUCHSTART) {
                        e.preventDefault();
                        // Starting the game for the first time.
                        if (!this.playing) {
                            // Started by touch so create a touch controller.
                            if (!this.touchController && e.type === Runner.events.TOUCHSTART) {
                                this.createTouchController();
                            }
                            this.loadSounds();
                            this.setPlayStatus(true);
                            this.update();
                            if (window.errorPageController) {
                                errorPageController.trackEasterEgg();
                            }
                        }
                        // Start jump.
                        if (!this.tRex.jumping && !this.tRex.ducking) {
                            this.playSound(this.soundFx.BUTTON_PRESS);
                            this.tRex.startJump(this.currentSpeed);
                        }
                    } else if (this.playing && Runner.keycodes.DUCK[e.keyCode]) {
                        e.preventDefault();
                        if (this.tRex.jumping) {
                            // Speed drop, activated only when jump key is not pressed.
                            this.tRex.setSpeedDrop();
                        } else if (!this.tRex.jumping && !this.tRex.ducking) {
                            // Duck.
                            this.tRex.setDuck(true);
                        }
                    }
                    // iOS only triggers touchstart and no pointer events.
                } else if (
                    IS_IOS && this.crashed && e.type === Runner.events.TOUCHSTART &&
                    e.currentTarget === this.containerEl) {
                    this.handleGameOverClicks(e);
                }
            }
        },

        /**
         * Process key up.
         * @param {Event} e
         */
        onKeyUp(e) {
            const keyCode = String(e.keyCode);
            const isjumpKey = Runner.keycodes.JUMP[keyCode] ||
                e.type === Runner.events.TOUCHEND || e.type === Runner.events.POINTERUP;

            if (this.isRunning() && isjumpKey) {
                this.tRex.endJump();
            } else if (Runner.keycodes.DUCK[keyCode]) {
                this.tRex.speedDrop = false;
                this.tRex.setDuck(false);
            } else if (this.crashed) {
                // Check that enough time has elapsed before allowing jump key to restart.
                const deltaTime = getTimeStamp() - this.time;

                if (this.isCanvasInView() &&
                    (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) ||
                        (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&
                            Runner.keycodes.JUMP[keyCode]))) {
                    this.handleGameOverClicks(e);
                }
            } else if (this.paused && isjumpKey) {
                // Reset the jump state
                this.tRex.reset();
                this.play();
            }
        },

        /**
         * Process gamepad connected event.
         * @param {Event} e
         */
        onGamepadConnected(e) {
            if (!this.pollingGamepads) {
                this.pollGamepadState();
            }
        },

        /**
         * rAF loop for gamepad polling.
         */
        pollGamepadState() {
            const gamepads = navigator.getGamepads();
            this.pollActiveGamepad(gamepads);

            this.pollingGamepads = true;
            requestAnimationFrame(this.pollGamepadState.bind(this));
        },

        /**
         * Polls for a gamepad with the jump button pressed. If one is found this
         * becomes the "active" gamepad and all others are ignored.
         * @param {!Array<Gamepad>} gamepads
         */
        pollForActiveGamepad(gamepads) {
            for (let i = 0; i < gamepads.length; ++i) {
                if (gamepads[i] && gamepads[i].buttons.length > 0 &&
                    gamepads[i].buttons[0].pressed) {
                    this.gamepadIndex = i;
                    this.pollActiveGamepad(gamepads);
                    return;
                }
            }
        },

        /**
         * Polls the chosen gamepad for button presses and generates KeyboardEvents
         * to integrate with the rest of the game logic.
         * @param {!Array<Gamepad>} gamepads
         */
        pollActiveGamepad(gamepads) {
            if (this.gamepadIndex === undefined) {
                this.pollForActiveGamepad(gamepads);
                return;
            }

            const gamepad = gamepads[this.gamepadIndex];
            if (!gamepad) {
                this.gamepadIndex = undefined;
                this.pollForActiveGamepad(gamepads);
                return;
            }

            // The gamepad specification defines the typical mapping of physical buttons
            // to button indicies: https://w3c.github.io/gamepad/#remapping
            this.pollGamepadButton(gamepad, 0, 38);  // Jump
            if (gamepad.buttons.length >= 2) {
                this.pollGamepadButton(gamepad, 1, 40);  // Duck
            }
            if (gamepad.buttons.length >= 10) {
                this.pollGamepadButton(gamepad, 9, 13);  // Restart
            }

            this.previousGamepad = gamepad;
        },

        /**
         * Generates a key event based on a gamepad button.
         * @param {!Gamepad} gamepad
         * @param {number} buttonIndex
         * @param {number} keyCode
         */
        pollGamepadButton(gamepad, buttonIndex, keyCode) {
            const state = gamepad.buttons[buttonIndex].pressed;
            let previousState = false;
            if (this.previousGamepad) {
                previousState = this.previousGamepad.buttons[buttonIndex].pressed;
            }
            // Generate key events on the rising and falling edge of a button press.
            if (state !== previousState) {
                const e = new KeyboardEvent(state ? Runner.events.KEYDOWN
                    : Runner.events.KEYUP,
                    { keyCode: keyCode });
                document.dispatchEvent(e);
            }
        },

        /**
         * Handle interactions on the game over screen state.
         * A user is able to tap the high score twice to reset it.
         * @param {Event} e
         */
        handleGameOverClicks(e) {
            e.preventDefault();
            if (this.distanceMeter.hasClickedOnHighScore(e) && this.highestScore) {
                if (this.distanceMeter.isHighScoreFlashing()) {
                    // Subsequent click, reset the high score.
                    this.saveHighScore(0, true);
                    this.distanceMeter.resetHighScore();
                } else {
                    // First click, flash the high score.
                    this.distanceMeter.startHighScoreFlashing();
                }
            } else {
                this.distanceMeter.cancelHighScoreFlashing();
                this.restart();
            }
        },

        /**
         * Returns whether the event was a left click on canvas.
         * On Windows right click is registered as a click.
         * @param {Event} e
         * @return {boolean}
         */
        isLeftClickOnCanvas(e) {
            return e.button != null && e.button < 2 &&
                e.type === Runner.events.POINTERUP && e.target === this.canvas;
        },

        /**
         * RequestAnimationFrame wrapper.
         */
        scheduleNextUpdate() {
            if (!this.updatePending) {
                this.updatePending = true;
                this.raqId = requestAnimationFrame(this.update.bind(this));
            }
        },

        /**
         * Whether the game is running.
         * @return {boolean}
         */
        isRunning() {
            return !!this.raqId;
        },

        /**
         * Set the initial high score as stored in the user's profile.
         * @param {number} highScore
         */
        initializeHighScore(highScore) {
            this.syncHighestScore = true;
            highScore = Math.ceil(highScore);
            if (highScore < this.highestScore) {
                if (window.errorPageController) {
                    errorPageController.updateEasterEggHighScore(this.highestScore);
                }
                return;
            }
            this.highestScore = highScore;
            this.distanceMeter.setHighScore(this.highestScore);
        },

        /**
         * Sets the current high score and saves to the profile if available.
         * @param {number} distanceRan Total distance ran.
         * @param {boolean=} opt_resetScore Whether to reset the score.
         */
        saveHighScore(distanceRan, opt_resetScore) {
            this.highestScore = Math.ceil(distanceRan);
            this.distanceMeter.setHighScore(this.highestScore);

            // Store the new high score in the profile.
            if (this.syncHighestScore && window.errorPageController) {
                if (opt_resetScore) {
                    errorPageController.resetEasterEggHighScore();
                } else {
                    errorPageController.updateEasterEggHighScore(this.highestScore);
                }
            }
        },

        /**
         * Game over state.
         */
        gameOver() {
            this.playSound(this.soundFx.HIT);
            vibrate(200);

            this.stop();
            this.crashed = true;
            this.distanceMeter.achievement = false;

            this.tRex.update(100, Trex.status.CRASHED);

            // Game over panel.
            if (!this.gameOverPanel) {
                if (this.canvas) {
                    this.gameOverPanel = new GameOverPanel(this.canvas,
                        this.spriteDef.TEXT_SPRITE, this.spriteDef.RESTART,
                        this.dimensions);
                }
            } else {
                this.gameOverPanel.draw();
            }

            // Update the high score.
            if (this.distanceRan > this.highestScore) {
                this.saveHighScore(this.distanceRan);
            }

            // Reset the time clock.
            this.time = getTimeStamp();
        },

        stop() {
            this.setPlayStatus(false);
            this.paused = true;
            cancelAnimationFrame(this.raqId);
            this.raqId = 0;
        },

        play() {
            if (!this.crashed) {
                this.setPlayStatus(true);
                this.paused = false;
                this.tRex.update(0, Trex.status.RUNNING);
                this.time = getTimeStamp();
                this.update();
            }
        },

        restart() {
            if (!this.raqId) {
                this.playCount++;
                this.runningTime = 0;
                this.setPlayStatus(true);
                this.paused = false;
                this.crashed = false;
                this.distanceRan = 0;
                this.setSpeed(this.config.SPEED);
                this.time = getTimeStamp();
                this.containerEl.classList.remove(Runner.classes.CRASHED);
                this.clearCanvas();
                this.distanceMeter.reset();
                this.horizon.reset();
                this.tRex.reset();
                this.playSound(this.soundFx.BUTTON_PRESS);
                this.invert(true);
                this.bdayFlashTimer = null;
                this.update();
            }
        },

        setPlayStatus(isPlaying) {
            if (this.touchController) {
                this.touchController.classList.toggle(HIDDEN_CLASS, !isPlaying);
            }
            this.playing = isPlaying;
        },

        /**
         * Whether the game should go into arcade mode.
         * @return {boolean}
         */
        isArcadeMode() {
            return document.title === ARCADE_MODE_URL;
        },

        /**
         * Hides offline messaging for a fullscreen game only experience.
         */
        setArcadeMode() {
            document.body.classList.add(Runner.classes.ARCADE_MODE);
            this.setArcadeModeContainerScale();
        },

        /**
         * Sets the scaling for arcade mode.
         */
        setArcadeModeContainerScale() {
            const windowHeight = window.innerHeight;
            const scaleHeight = windowHeight / this.dimensions.HEIGHT;
            const scaleWidth = window.innerWidth / this.dimensions.WIDTH;
            const scale = Math.max(1, Math.min(scaleHeight, scaleWidth));
            const scaledCanvasHeight = this.dimensions.HEIGHT * scale;
            // Positions the game container at 10% of the available vertical window
            // height minus the game container height.
            const translateY = Math.ceil(Math.max(0, (windowHeight - scaledCanvasHeight -
                Runner.config.ARCADE_MODE_INITIAL_TOP_POSITION) *
                Runner.config.ARCADE_MODE_TOP_POSITION_PERCENT)) *
                window.devicePixelRatio;
            this.containerEl.style.transform = 'scale(' + scale + ') translateY(' +
                translateY + 'px)';
        },

        /**
         * Pause the game if the tab is not in focus.
         */
        onVisibilityChange(e) {
            if (document.hidden || document.webkitHidden || e.type === 'blur' ||
                document.visibilityState !== 'visible') {
                this.stop();
            } else if (!this.crashed) {
                this.tRex.reset();
                this.play();
            }
        },

        /**
         * Play a sound.
         * @param {AudioBuffer} soundBuffer
         */
        playSound(soundBuffer) {
            if (soundBuffer) {
                const sourceNode = this.audioContext.createBufferSource();
                sourceNode.buffer = soundBuffer;
                sourceNode.connect(this.audioContext.destination);
                sourceNode.start(0);
            }
        },

        /**
         * Inverts the current page / canvas colors.
         * @param {boolean} reset Whether to reset colors.
         */
        invert(reset) {
            const htmlEl = document.firstElementChild;

            if (reset) {
                htmlEl.classList.toggle(Runner.classes.INVERTED,
                    false);
                this.invertTimer = 0;
                this.inverted = false;
            } else {
                this.inverted = htmlEl.classList.toggle(
                    Runner.classes.INVERTED, this.invertTrigger);
            }
        }
    };


    /**
     * Updates the canvas size taking into
     * account the backing store pixel ratio and
     * the device pixel ratio.
     *
     * See article by Paul Lewis:
     * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
     *
     * @param {HTMLCanvasElement} canvas
     * @param {number=} opt_width
     * @param {number=} opt_height
     * @return {boolean} Whether the canvas was scaled.
     */
    Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) {
        const context =
            /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));

        // Query the various pixel ratios
        const devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;
        /** @suppress {missingProperties} */
        const backingStoreRatio =
            Math.floor(context.webkitBackingStorePixelRatio) || 1;
        const ratio = devicePixelRatio / backingStoreRatio;

        // Upscale the canvas if the two ratios don't match
        if (devicePixelRatio !== backingStoreRatio) {
            const oldWidth = opt_width || canvas.width;
            const oldHeight = opt_height || canvas.height;

            canvas.width = oldWidth * ratio;
            canvas.height = oldHeight * ratio;

            canvas.style.width = oldWidth + 'px';
            canvas.style.height = oldHeight + 'px';

            // Scale the context to counter the fact that we've manually scaled
            // our canvas element.
            context.scale(ratio, ratio);
            return true;
        } else if (devicePixelRatio === 1) {
            // Reset the canvas width / height. Fixes scaling bug when the page is
            // zoomed and the devicePixelRatio changes accordingly.
            canvas.style.width = canvas.width + 'px';
            canvas.style.height = canvas.height + 'px';
        }
        return false;
    };


    /**
     * Get random number.
     * @param {number} min
     * @param {number} max
     */
    function getRandomNum(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }


    /**
     * Vibrate on mobile devices.
     * @param {number} duration Duration of the vibration in milliseconds.
     */
    function vibrate(duration) {
        if (IS_MOBILE && window.navigator.vibrate) {
            window.navigator.vibrate(duration);
        }
    }


    /**
     * Create canvas element.
     * @param {Element} container Element to append canvas to.
     * @param {number} width
     * @param {number} height
     * @param {string=} opt_classname
     * @return {HTMLCanvasElement}
     */
    function createCanvas(container, width, height, opt_classname) {
        const canvas =
            /** @type {!HTMLCanvasElement} */ (document.createElement('canvas'));
        canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' +
            opt_classname : Runner.classes.CANVAS;
        canvas.width = width;
        canvas.height = height;
        container.appendChild(canvas);

        return canvas;
    }


    /**
     * Decodes the base 64 audio to ArrayBuffer used by Web Audio.
     * @param {string} base64String
     */
    function decodeBase64ToArrayBuffer(base64String) {
        const len = (base64String.length / 4) * 3;
        const str = atob(base64String);
        const arrayBuffer = new ArrayBuffer(len);
        const bytes = new Uint8Array(arrayBuffer);

        for (let i = 0; i < len; i++) {
            bytes[i] = str.charCodeAt(i);
        }
        return bytes.buffer;
    }


    /**
     * Return the current timestamp.
     * @return {number}
     */
    function getTimeStamp() {
        return IS_IOS ? new Date().getTime() : performance.now();
    }


    //******************************************************************************


    /**
     * Game over panel.
     * @param {!HTMLCanvasElement} canvas
     * @param {Object} textImgPos
     * @param {Object} restartImgPos
     * @param {!Object} dimensions Canvas dimensions.
     * @constructor
     */
    function GameOverPanel(canvas, textImgPos, restartImgPos, dimensions) {
        this.canvas = canvas;
        this.canvasCtx =
            /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
        this.canvasDimensions = dimensions;
        this.textImgPos = textImgPos;
        this.restartImgPos = restartImgPos;
        this.draw();
    }


    /**
     * Dimensions used in the panel.
     * @enum {number}
     */
    GameOverPanel.dimensions = {
        TEXT_X: 0,
        TEXT_Y: 13,
        TEXT_WIDTH: 191,
        TEXT_HEIGHT: 11,
        RESTART_WIDTH: 36,
        RESTART_HEIGHT: 32
    };


    GameOverPanel.prototype = {
        /**
         * Update the panel dimensions.
         * @param {number} width New canvas width.
         * @param {number} opt_height Optional new canvas height.
         */
        updateDimensions(width, opt_height) {
            this.canvasDimensions.WIDTH = width;
            if (opt_height) {
                this.canvasDimensions.HEIGHT = opt_height;
            }
        },

        /**
         * Draw the panel.
         */
        draw() {
            const dimensions = GameOverPanel.dimensions;

            const centerX = this.canvasDimensions.WIDTH / 2;

            // Game over text.
            let textSourceX = dimensions.TEXT_X;
            let textSourceY = dimensions.TEXT_Y;
            let textSourceWidth = dimensions.TEXT_WIDTH;
            let textSourceHeight = dimensions.TEXT_HEIGHT;

            const textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
            const textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
            const textTargetWidth = dimensions.TEXT_WIDTH;
            const textTargetHeight = dimensions.TEXT_HEIGHT;

            let restartSourceWidth = dimensions.RESTART_WIDTH;
            let restartSourceHeight = dimensions.RESTART_HEIGHT;
            const restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2);
            const restartTargetY = this.canvasDimensions.HEIGHT / 2;

            if (IS_HIDPI) {
                textSourceY *= 2;
                textSourceX *= 2;
                textSourceWidth *= 2;
                textSourceHeight *= 2;
                restartSourceWidth *= 2;
                restartSourceHeight *= 2;
            }

            textSourceX += this.textImgPos.x;
            textSourceY += this.textImgPos.y;

            // Game over text from sprite.
            this.canvasCtx.drawImage(Runner.imageSprite,
                textSourceX, textSourceY, textSourceWidth, textSourceHeight,
                textTargetX, textTargetY, textTargetWidth, textTargetHeight);

            // Restart button.
            this.canvasCtx.drawImage(Runner.imageSprite,
                this.restartImgPos.x, this.restartImgPos.y,
                restartSourceWidth, restartSourceHeight,
                restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
                dimensions.RESTART_HEIGHT);
        }
    };


    //******************************************************************************

    /**
     * Check for a collision.
     * @param {!Obstacle} obstacle
     * @param {!Trex} tRex T-rex object.
     * @param {CanvasRenderingContext2D=} opt_canvasCtx Optional canvas context for
     *    drawing collision boxes.
     * @return {Array<CollisionBox>|undefined}
     */
    function checkForCollision(obstacle, tRex, opt_canvasCtx) {
        const obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos;

        // Adjustments are made to the bounding box as there is a 1 pixel white
        // border around the t-rex and obstacles.
        const tRexBox = new CollisionBox(
            tRex.xPos + 1,
            tRex.yPos + 1,
            tRex.config.WIDTH - 2,
            tRex.config.HEIGHT - 2);

        const obstacleBox = new CollisionBox(
            obstacle.xPos + 1,
            obstacle.yPos + 1,
            obstacle.typeConfig.width * obstacle.size - 2,
            obstacle.typeConfig.height - 2);

        // Debug outer box
        if (opt_canvasCtx) {
            drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
        }

        // Simple outer bounds check.
        if (boxCompare(tRexBox, obstacleBox)) {
            const collisionBoxes = obstacle.collisionBoxes;
            const tRexCollisionBoxes = tRex.ducking ?
                Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING;

            // Detailed axis aligned box check.
            for (let t = 0; t < tRexCollisionBoxes.length; t++) {
                for (let i = 0; i < collisionBoxes.length; i++) {
                    // Adjust the box to actual positions.
                    const adjTrexBox =
                        createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox);
                    const adjObstacleBox =
                        createAdjustedCollisionBox(collisionBoxes[i], obstacleBox);
                    const crashed = boxCompare(adjTrexBox, adjObstacleBox);

                    // Draw boxes for debug.
                    if (opt_canvasCtx) {
                        drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox);
                    }

                    if (crashed) {
                        return [adjTrexBox, adjObstacleBox];
                    }
                }
            }
        }
    }


    /**
     * Adjust the collision box.
     * @param {!CollisionBox} box The original box.
     * @param {!CollisionBox} adjustment Adjustment box.
     * @return {CollisionBox} The adjusted collision box object.
     */
    function createAdjustedCollisionBox(box, adjustment) {
        return new CollisionBox(
            box.x + adjustment.x,
            box.y + adjustment.y,
            box.width,
            box.height);
    }


    /**
     * Draw the collision boxes for debug.
     */
    function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) {
        canvasCtx.save();
        canvasCtx.strokeStyle = '#f00';
        canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height);

        canvasCtx.strokeStyle = '#0f0';
        canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,
            obstacleBox.width, obstacleBox.height);
        canvasCtx.restore();
    }


    /**
     * Compare two collision boxes for a collision.
     * @param {CollisionBox} tRexBox
     * @param {CollisionBox} obstacleBox
     * @return {boolean} Whether the boxes intersected.
     */
    function boxCompare(tRexBox, obstacleBox) {
        let crashed = false;
        const tRexBoxX = tRexBox.x;
        const tRexBoxY = tRexBox.y;

        const obstacleBoxX = obstacleBox.x;
        const obstacleBoxY = obstacleBox.y;

        // Axis-Aligned Bounding Box method.
        if (tRexBox.x < obstacleBoxX + obstacleBox.width &&
            tRexBox.x + tRexBox.width > obstacleBoxX &&
            tRexBox.y < obstacleBox.y + obstacleBox.height &&
            tRexBox.height + tRexBox.y > obstacleBox.y) {
            crashed = true;
        }

        return crashed;
    }


    //******************************************************************************

    /**
     * Collision box object.
     * @param {number} x X position.
     * @param {number} y Y Position.
     * @param {number} w Width.
     * @param {number} h Height.
     * @constructor
     */
    function CollisionBox(x, y, w, h) {
        this.x = x;
        this.y = y;
        this.width = w;
        this.height = h;
    }


    //******************************************************************************

    /**
     * Obstacle.
     * @param {CanvasRenderingContext2D} canvasCtx
     * @param {ObstacleType} type
     * @param {Object} spriteImgPos Obstacle position in sprite.
     * @param {Object} dimensions
     * @param {number} gapCoefficient Mutipler in determining the gap.
     * @param {number} speed
     * @param {number=} opt_xOffset
     * @constructor
     */
    function Obstacle(canvasCtx, type, spriteImgPos, dimensions,
                      gapCoefficient, speed, opt_xOffset) {

        this.canvasCtx = canvasCtx;
        this.spritePos = spriteImgPos;
        this.typeConfig = type;
        this.gapCoefficient = gapCoefficient;
        this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH);
        this.dimensions = dimensions;
        this.remove = false;
        this.xPos = dimensions.WIDTH + (opt_xOffset || 0);
        this.yPos = 0;
        this.width = 0;
        this.collisionBoxes = [];
        this.gap = 0;
        this.speedOffset = 0;

        // For animated obstacles.
        this.currentFrame = 0;
        this.timer = 0;

        this.init(speed);
    }

    /**
     * Coefficient for calculating the maximum gap.
     * @const
     */
    Obstacle.MAX_GAP_COEFFICIENT = 1.5;

    /**
     * Maximum obstacle grouping count.
     * @const
     */
    Obstacle.MAX_OBSTACLE_LENGTH = 3;


    Obstacle.prototype = {
        /**
         * Initialise the DOM for the obstacle.
         * @param {number} speed
         */
        init(speed) {
            this.cloneCollisionBoxes();

            // Only allow sizing if we're at the right speed.
            if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
                this.size = 1;
            }

            this.width = this.typeConfig.width * this.size;

            // Check if obstacle can be positioned at various heights.
            if (Array.isArray(this.typeConfig.yPos)) {
                const yPosConfig =
                    IS_MOBILE ? this.typeConfig.yPosMobile : this.typeConfig.yPos;
                this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
            } else {
                this.yPos = this.typeConfig.yPos;
            }

            this.draw();

            // Make collision box adjustments,
            // Central box is adjusted to the size as one box.
            //      ____        ______        ________
            //    _|   |-|    _|     |-|    _|       |-|
            //   | |<->| |   | |<--->| |   | |<----->| |
            //   | | 1 | |   | |  2  | |   | |   3   | |
            //   |_|___|_|   |_|_____|_|   |_|_______|_|
            //
            if (this.size > 1) {
                this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
                    this.collisionBoxes[2].width;
                this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
            }

            // For obstacles that go at a different speed from the horizon.
            if (this.typeConfig.speedOffset) {
                this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
                    -this.typeConfig.speedOffset;
            }

            this.gap = this.getGap(this.gapCoefficient, speed);
        },

        /**
         * Draw and crop based on size.
         */
        draw() {
            let sourceWidth = this.typeConfig.width;
            let sourceHeight = this.typeConfig.height;

            if (IS_HIDPI) {
                sourceWidth = sourceWidth * 2;
                sourceHeight = sourceHeight * 2;
            }

            // X position in sprite.
            let sourceX =
                (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x;

            // Animation frames.
            if (this.currentFrame > 0) {
                sourceX += sourceWidth * this.currentFrame;
            }

            this.canvasCtx.drawImage(
                Runner.imageSprite, sourceX, this.spritePos.y, sourceWidth * this.size,
                sourceHeight, this.xPos, this.yPos, this.typeConfig.width * this.size,
                this.typeConfig.height);
        },

        /**
         * Obstacle frame update.
         * @param {number} deltaTime
         * @param {number} speed
         */
        update(deltaTime, speed) {
            if (!this.remove) {
                if (this.typeConfig.speedOffset) {
                    speed += this.speedOffset;
                }
                this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);

                // Update frame
                if (this.typeConfig.numFrames) {
                    this.timer += deltaTime;
                    if (this.timer >= this.typeConfig.frameRate) {
                        this.currentFrame =
                            this.currentFrame === this.typeConfig.numFrames - 1 ?
                                0 :
                                this.currentFrame + 1;
                        this.timer = 0;
                    }
                }
                this.draw();

                if (!this.isVisible()) {
                    this.remove = true;
                }
            }
        },

        /**
         * Calculate a random gap size.
         * - Minimum gap gets wider as speed increses
         * @param {number} gapCoefficient
         * @param {number} speed
         * @return {number} The gap size.
         */
        getGap(gapCoefficient, speed) {
            const minGap = Math.round(
                this.width * speed + this.typeConfig.minGap * gapCoefficient);
            const maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
            return getRandomNum(minGap, maxGap);
        },

        /**
         * Check if obstacle is visible.
         * @return {boolean} Whether the obstacle is in the game area.
         */
        isVisible() {
            return this.xPos + this.width > 0;
        },

        /**
         * Make a copy of the collision boxes, since these will change based on
         * obstacle type and size.
         */
        cloneCollisionBoxes() {
            const collisionBoxes = this.typeConfig.collisionBoxes;

            for (let i = collisionBoxes.length - 1; i >= 0; i--) {
                this.collisionBoxes[i] = new CollisionBox(
                    collisionBoxes[i].x, collisionBoxes[i].y, collisionBoxes[i].width,
                    collisionBoxes[i].height);
            }
        }
    };

    /**
     * Obstacle definitions.
     * minGap: minimum pixel space betweeen obstacles.
     * multipleSpeed: Speed at which multiples are allowed.
     * speedOffset: speed faster / slower than the horizon.
     * minSpeed: Minimum speed which the obstacle can make an appearance.
     *
     * @typedef {{
     *   type: string,
     *   width: number,
     *   height: number,
     *   yPos: number,
     *   multipleSpeed: number,
     *   minGap: number,
     *   minSpeed: number,
     *   collisionBoxes: Array<CollisionBox>,
     * }}
     */
    let ObstacleType;

    /** @type {Array<ObstacleType>} */
    Obstacle.types = [
        {
            type: 'CACTUS_SMALL',
            width: 17,
            height: 35,
            yPos: 105,
            multipleSpeed: 4,
            minGap: 120,
            minSpeed: 0,
            collisionBoxes: [
                new CollisionBox(0, 7, 5, 27),
                new CollisionBox(4, 0, 6, 34),
                new CollisionBox(10, 4, 7, 14)
            ]
        },
        {
            type: 'CACTUS_LARGE',
            width: 25,
            height: 50,
            yPos: 90,
            multipleSpeed: 7,
            minGap: 120,
            minSpeed: 0,
            collisionBoxes: [
                new CollisionBox(0, 12, 7, 38),
                new CollisionBox(8, 0, 7, 49),
                new CollisionBox(13, 10, 10, 38)
            ]
        },
        {
            type: 'PTERODACTYL',
            width: 46,
            height: 40,
            yPos: [ 100, 75, 50 ], // Variable height.
            yPosMobile: [ 100, 50 ], // Variable height mobile.
            multipleSpeed: 999,
            minSpeed: 8.5,
            minGap: 150,
            collisionBoxes: [
                new CollisionBox(15, 15, 16, 5),
                new CollisionBox(18, 21, 24, 6),
                new CollisionBox(2, 14, 4, 3),
                new CollisionBox(6, 10, 4, 7),
                new CollisionBox(10, 8, 6, 9)
            ],
            numFrames: 2,
            frameRate: 1000/6,
            speedOffset: .8
        }
    ];


    //******************************************************************************
    /**
     * T-rex game character.
     * @param {HTMLCanvasElement} canvas
     * @param {Object} spritePos Positioning within image sprite.
     * @constructor
     */
    function Trex(canvas, spritePos) {
        this.canvas = canvas;
        this.canvasCtx =
            /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
        this.spritePos = spritePos;
        this.xPos = 0;
        this.yPos = 0;
        this.xInitialPos = 0;
        // Position when on the ground.
        this.groundYPos = 0;
        this.currentFrame = 0;
        this.currentAnimFrames = [];
        this.blinkDelay = 0;
        this.blinkCount = 0;
        this.animStartTime = 0;
        this.timer = 0;
        this.msPerFrame = 1000 / FPS;
        this.config = Trex.config;
        // Current status.
        this.status = Trex.status.WAITING;
        this.jumping = false;
        this.ducking = false;
        this.jumpVelocity = 0;
        this.reachedMinHeight = false;
        this.speedDrop = false;
        this.jumpCount = 0;
        this.jumpspotX = 0;

        this.init();
    }


    /**
     * T-rex player config.
     */
    Trex.config = {
        DROP_VELOCITY: -5,
        GRAVITY: 0.6,
        HEIGHT: 47,
        HEIGHT_DUCK: 25,
        INIITAL_JUMP_VELOCITY: -10,
        INTRO_DURATION: 1500,
        MAX_JUMP_HEIGHT: 30,
        MIN_JUMP_HEIGHT: 30,
        SPEED_DROP_COEFFICIENT: 3,
        SPRITE_WIDTH: 262,
        START_X_POS: 50,
        WIDTH: 44,
        WIDTH_DUCK: 59
    };


    /**
     * Used in collision detection.
     * @enum {Array<CollisionBox>}
     */
    Trex.collisionBoxes = {
        DUCKING: [
            new CollisionBox(1, 18, 55, 25)
        ],
        RUNNING: [
            new CollisionBox(22, 0, 17, 16),
            new CollisionBox(1, 18, 30, 9),
            new CollisionBox(10, 35, 14, 8),
            new CollisionBox(1, 24, 29, 5),
            new CollisionBox(5, 30, 21, 4),
            new CollisionBox(9, 34, 15, 4)
        ]
    };


    /**
     * Animation states.
     * @enum {string}
     */
    Trex.status = {
        CRASHED: 'CRASHED',
        DUCKING: 'DUCKING',
        JUMPING: 'JUMPING',
        RUNNING: 'RUNNING',
        WAITING: 'WAITING'
    };

    /**
     * Blinking coefficient.
     * @const
     */
    Trex.BLINK_TIMING = 7000;


    /**
     * Animation config for different states.
     * @enum {Object}
     */
    Trex.animFrames = {
        WAITING: {
            frames: [44, 0],
            msPerFrame: 1000 / 3
        },
        RUNNING: {
            frames: [88, 132],
            msPerFrame: 1000 / 12
        },
        CRASHED: {
            frames: [220],
            msPerFrame: 1000 / 60
        },
        JUMPING: {
            frames: [0],
            msPerFrame: 1000 / 60
        },
        DUCKING: {
            frames: [264, 323],
            msPerFrame: 1000 / 8
        }
    };


    Trex.prototype = {
        /**
         * T-rex player initaliser.
         * Sets the t-rex to blink at random intervals.
         */
        init() {
            this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
                Runner.config.BOTTOM_PAD;
            this.yPos = this.groundYPos;
            this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;

            this.draw(0, 0);
            this.update(0, Trex.status.WAITING);
        },

        /**
         * Setter for the jump velocity.
         * The approriate drop velocity is also set.
         * @param {number} setting
         */
        setJumpVelocity(setting) {
            this.config.INIITAL_JUMP_VELOCITY = -setting;
            this.config.DROP_VELOCITY = -setting / 2;
        },

        /**
         * Set the animation status.
         * @param {!number} deltaTime
         * @param {Trex.status=} opt_status Optional status to switch to.
         */
        update(deltaTime, opt_status) {
            this.timer += deltaTime;

            // Update the status.
            if (opt_status) {
                this.status = opt_status;
                this.currentFrame = 0;
                this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
                this.currentAnimFrames = Trex.animFrames[opt_status].frames;

                if (opt_status === Trex.status.WAITING) {
                    this.animStartTime = getTimeStamp();
                    this.setBlinkDelay();
                }
            }

            // Game intro animation, T-rex moves in from the left.
            if (this.playingIntro && this.xPos < this.config.START_X_POS) {
                this.xPos += Math.round((this.config.START_X_POS /
                    this.config.INTRO_DURATION) * deltaTime);
                this.xInitialPos = this.xPos;
            }

            if (this.status === Trex.status.WAITING) {
                this.blink(getTimeStamp());
            } else {
                this.draw(this.currentAnimFrames[this.currentFrame], 0);
            }

            // Update the frame position.
            if (this.timer >= this.msPerFrame) {
                this.currentFrame = this.currentFrame ==
                this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
                this.timer = 0;
            }

            // Speed drop becomes duck if the down key is still being pressed.
            if (this.speedDrop && this.yPos === this.groundYPos) {
                this.speedDrop = false;
                this.setDuck(true);
            }
        },

        /**
         * Draw the t-rex to a particular position.
         * @param {number} x
         * @param {number} y
         */
        draw(x, y) {
            let sourceX = x;
            let sourceY = y;
            let sourceWidth = this.ducking && this.status !== Trex.status.CRASHED ?
                this.config.WIDTH_DUCK :
                this.config.WIDTH;
            let sourceHeight = this.config.HEIGHT;
            const outputHeight = sourceHeight;

            if (IS_HIDPI) {
                sourceX *= 2;
                sourceY *= 2;
                sourceWidth *= 2;
                sourceHeight *= 2;
            }

            // Adjustments for sprite sheet position.
            sourceX += this.spritePos.x;
            sourceY += this.spritePos.y;

            // Ducking.
            if (this.ducking && this.status !== Trex.status.CRASHED) {
                this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
                    sourceWidth, sourceHeight,
                    this.xPos, this.yPos,
                    this.config.WIDTH_DUCK, outputHeight);
            } else {
                // Crashed whilst ducking. Trex is standing up so needs adjustment.
                if (this.ducking && this.status === Trex.status.CRASHED) {
                    this.xPos++;
                }
                // Standing / running
                this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
                    sourceWidth, sourceHeight,
                    this.xPos, this.yPos,
                    this.config.WIDTH, outputHeight);
            }
            this.canvasCtx.globalAlpha = 1;
        },

        /**
         * Sets a random time for the blink to happen.
         */
        setBlinkDelay() {
            this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
        },

        /**
         * Make t-rex blink at random intervals.
         * @param {number} time Current time in milliseconds.
         */
        blink(time) {
            const deltaTime = time - this.animStartTime;

            if (deltaTime >= this.blinkDelay) {
                this.draw(this.currentAnimFrames[this.currentFrame], 0);

                if (this.currentFrame === 1) {
                    // Set new random delay to blink.
                    this.setBlinkDelay();
                    this.animStartTime = time;
                    this.blinkCount++;
                }
            }
        },

        /**
         * Initialise a jump.
         * @param {number} speed
         */
        startJump(speed) {
            if (!this.jumping) {
                this.update(0, Trex.status.JUMPING);
                // Tweak the jump velocity based on the speed.
                this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10);
                this.jumping = true;
                this.reachedMinHeight = false;
                this.speedDrop = false;
            }
        },

        /**
         * Jump is complete, falling down.
         */
        endJump() {
            if (this.reachedMinHeight &&
                this.jumpVelocity < this.config.DROP_VELOCITY) {
                this.jumpVelocity = this.config.DROP_VELOCITY;
            }
        },

        /**
         * Update frame for a jump.
         * @param {number} deltaTime
         */
        updateJump(deltaTime) {
            const msPerFrame = Trex.animFrames[this.status].msPerFrame;
            const framesElapsed = deltaTime / msPerFrame;

            // Speed drop makes Trex fall faster.
            if (this.speedDrop) {
                this.yPos += Math.round(this.jumpVelocity *
                    this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
            } else {
                this.yPos += Math.round(this.jumpVelocity * framesElapsed);
            }

            this.jumpVelocity += this.config.GRAVITY * framesElapsed;

            // Minimum height has been reached.
            if (this.yPos < this.minJumpHeight || this.speedDrop) {
                this.reachedMinHeight = true;
            }

            // Reached max height
            if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
                this.endJump();
            }

            // Back down at ground level. Jump completed.
            if (this.yPos > this.groundYPos) {
                this.reset();
                this.jumpCount++;
            }
        },

        /**
         * Set the speed drop. Immediately cancels the current jump.
         */
        setSpeedDrop() {
            this.speedDrop = true;
            this.jumpVelocity = 1;
        },

        /**
         * @param {boolean} isDucking
         */
        setDuck(isDucking) {
            if (isDucking && this.status !== Trex.status.DUCKING) {
                this.update(0, Trex.status.DUCKING);
                this.ducking = true;
            } else if (this.status === Trex.status.DUCKING) {
                this.update(0, Trex.status.RUNNING);
                this.ducking = false;
            }
        },

        /**
         * Reset the t-rex to running at start of game.
         */
        reset() {
            this.xPos = this.xInitialPos;
            this.yPos = this.groundYPos;
            this.jumpVelocity = 0;
            this.jumping = false;
            this.ducking = false;
            this.update(0, Trex.status.RUNNING);
            this.midair = false;
            this.speedDrop = false;
            this.jumpCount = 0;
        }
    };


    //******************************************************************************

    /**
     * Handles displaying the distance meter.
     * @param {!HTMLCanvasElement} canvas
     * @param {Object} spritePos Image position in sprite.
     * @param {number} canvasWidth
     * @constructor
     */
    function DistanceMeter(canvas, spritePos, canvasWidth) {
        this.canvas = canvas;
        this.canvasCtx =
            /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
        this.image = Runner.imageSprite;
        this.spritePos = spritePos;
        this.x = 0;
        this.y = 5;

        this.currentDistance = 0;
        this.maxScore = 0;
        this.highScore = '0';
        this.container = null;

        this.digits = [];
        this.achievement = false;
        this.defaultString = '';
        this.flashTimer = 0;
        this.flashIterations = 0;
        this.invertTrigger = false;
        this.flashingRafId = null;
        this.highScoreBounds = {};
        this.highScoreFlashing = false;

        this.config = DistanceMeter.config;
        this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS;
        this.init(canvasWidth);
    }


    /**
     * @enum {number}
     */
    DistanceMeter.dimensions = {
        WIDTH: 10,
        HEIGHT: 13,
        DEST_WIDTH: 11
    };


    /**
     * Y positioning of the digits in the sprite sheet.
     * X position is always 0.
     * @type {Array<number>}
     */
    DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];


    /**
     * Distance meter config.
     * @enum {number}
     */
    DistanceMeter.config = {
        // Number of digits.
        MAX_DISTANCE_UNITS: 5,

        // Distance that causes achievement animation.
        ACHIEVEMENT_DISTANCE: 100,

        // Used for conversion from pixel distance to a scaled unit.
        COEFFICIENT: 0.025,

        // Flash duration in milliseconds.
        FLASH_DURATION: 1000 / 4,

        // Flash iterations for achievement animation.
        FLASH_ITERATIONS: 3,

        // Padding around the high score hit area.
        HIGH_SCORE_HIT_AREA_PADDING: 4
    };


    DistanceMeter.prototype = {
        /**
         * Initialise the distance meter to '00000'.
         * @param {number} width Canvas width in px.
         */
        init(width) {
            let maxDistanceStr = '';

            this.calcXPos(width);
            this.maxScore = this.maxScoreUnits;
            for (let i = 0; i < this.maxScoreUnits; i++) {
                this.draw(i, 0);
                this.defaultString += '0';
                maxDistanceStr += '9';
            }

            this.maxScore = parseInt(maxDistanceStr, 10);
        },

        /**
         * Calculate the xPos in the canvas.
         * @param {number} canvasWidth
         */
        calcXPos(canvasWidth) {
            this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
                (this.maxScoreUnits + 1));
        },

        /**
         * Draw a digit to canvas.
         * @param {number} digitPos Position of the digit.
         * @param {number} value Digit value 0-9.
         * @param {boolean=} opt_highScore Whether drawing the high score.
         */
        draw(digitPos, value, opt_highScore) {
            let sourceWidth = DistanceMeter.dimensions.WIDTH;
            let sourceHeight = DistanceMeter.dimensions.HEIGHT;
            let sourceX = DistanceMeter.dimensions.WIDTH * value;
            let sourceY = 0;

            const targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
            const targetY = this.y;
            const targetWidth = DistanceMeter.dimensions.WIDTH;
            const targetHeight = DistanceMeter.dimensions.HEIGHT;

            // For high DPI we 2x source values.
            if (IS_HIDPI) {
                sourceWidth *= 2;
                sourceHeight *= 2;
                sourceX *= 2;
            }

            sourceX += this.spritePos.x;
            sourceY += this.spritePos.y;

            this.canvasCtx.save();

            if (opt_highScore) {
                // Left of the current score.
                const highScoreX = this.x - (this.maxScoreUnits * 2) *
                    DistanceMeter.dimensions.WIDTH;
                this.canvasCtx.translate(highScoreX, this.y);
            } else {
                this.canvasCtx.translate(this.x, this.y);
            }

            this.canvasCtx.drawImage(this.image, sourceX, sourceY,
                sourceWidth, sourceHeight,
                targetX, targetY,
                targetWidth, targetHeight
            );

            this.canvasCtx.restore();
        },

        /**
         * Covert pixel distance to a 'real' distance.
         * @param {number} distance Pixel distance ran.
         * @return {number} The 'real' distance ran.
         */
        getActualDistance(distance) {
            return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
        },

        /**
         * Update the distance meter.
         * @param {number} distance
         * @param {number} deltaTime
         * @return {boolean} Whether the acheivement sound fx should be played.
         */
        update(deltaTime, distance) {
            let paint = true;
            let playSound = false;

            if (!this.achievement) {
                distance = this.getActualDistance(distance);
                // Score has gone beyond the initial digit count.
                if (distance > this.maxScore && this.maxScoreUnits ==
                    this.config.MAX_DISTANCE_UNITS) {
                    this.maxScoreUnits++;
                    this.maxScore = parseInt(this.maxScore + '9', 10);
                } else {
                    this.distance = 0;
                }

                if (distance > 0) {
                    // Achievement unlocked.
                    if (distance % this.config.ACHIEVEMENT_DISTANCE === 0) {
                        // Flash score and play sound.
                        this.achievement = true;
                        this.flashTimer = 0;
                        playSound = true;
                    }

                    // Create a string representation of the distance with leading 0.
                    const distanceStr = (this.defaultString +
                        distance).substr(-this.maxScoreUnits);
                    this.digits = distanceStr.split('');
                } else {
                    this.digits = this.defaultString.split('');
                }
            } else {
                // Control flashing of the score on reaching acheivement.
                if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
                    this.flashTimer += deltaTime;

                    if (this.flashTimer < this.config.FLASH_DURATION) {
                        paint = false;
                    } else if (this.flashTimer >
                        this.config.FLASH_DURATION * 2) {
                        this.flashTimer = 0;
                        this.flashIterations++;
                    }
                } else {
                    this.achievement = false;
                    this.flashIterations = 0;
                    this.flashTimer = 0;
                }
            }

            // Draw the digits if not flashing.
            if (paint) {
                for (let i = this.digits.length - 1; i >= 0; i--) {
                    this.draw(i, parseInt(this.digits[i], 10));
                }
            }

            this.drawHighScore();
            return playSound;
        },

        /**
         * Draw the high score.
         */
        drawHighScore() {
            this.canvasCtx.save();
            this.canvasCtx.globalAlpha = .8;
            for (let i = this.highScore.length - 1; i >= 0; i--) {
                this.draw(i, parseInt(this.highScore[i], 10), true);
            }
            this.canvasCtx.restore();
        },

        /**
         * Set the highscore as a array string.
         * Position of char in the sprite: H - 10, I - 11.
         * @param {number} distance Distance ran in pixels.
         */
        setHighScore(distance) {
            distance = this.getActualDistance(distance);
            const highScoreStr = (this.defaultString +
                distance).substr(-this.maxScoreUnits);

            this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
        },


        /**
         * Whether a clicked is in the high score area.
         * @param {Event} e Event object.
         * @return {boolean} Whether the click was in the high score bounds.
         */
        hasClickedOnHighScore(e) {
            let x = 0;
            let y = 0;

            if (e.touches) {
                // Bounds for touch differ from pointer.
                const canvasBounds = this.canvas.getBoundingClientRect();
                x = e.touches[0].clientX - canvasBounds.left;
                y = e.touches[0].clientY - canvasBounds.top;
            } else {
                x = e.offsetX;
                y = e.offsetY;
            }

            this.highScoreBounds = this.getHighScoreBounds();
            return x >= this.highScoreBounds.x && x <=
                this.highScoreBounds.x + this.highScoreBounds.width &&
                y >= this.highScoreBounds.y && y <=
                this.highScoreBounds.y + this.highScoreBounds.height;
        },

        /**
         * Get the bounding box for the high score.
         * @return {Object} Object with x, y, width and height properties.
         */
        getHighScoreBounds() {
            return {
                x: (this.x - (this.maxScoreUnits * 2) *
                    DistanceMeter.dimensions.WIDTH) -
                    DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING,
                y: this.y,
                width: DistanceMeter.dimensions.WIDTH * (this.highScore.length + 1) +
                    DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING,
                height: DistanceMeter.dimensions.HEIGHT +
                    (DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING * 2)
            };
        },

        /**
         * Animate flashing the high score to indicate ready for resetting.
         * The flashing stops following this.config.FLASH_ITERATIONS x 2 flashes.
         */
        flashHighScore() {
            const now = getTimeStamp();
            const deltaTime = now - (this.frameTimeStamp || now);
            let paint = true;
            this.frameTimeStamp = now;

            // Reached the max number of flashes.
            if (this.flashIterations > this.config.FLASH_ITERATIONS * 2) {
                this.cancelHighScoreFlashing();
                return;
            }

            this.flashTimer += deltaTime;

            if (this.flashTimer < this.config.FLASH_DURATION) {
                paint = false;
            } else if (this.flashTimer > this.config.FLASH_DURATION * 2) {
                this.flashTimer = 0;
                this.flashIterations++;
            }

            if (paint) {
                this.drawHighScore();
            } else {
                this.clearHighScoreBounds();
            }
            // Frame update.
            this.flashingRafId =
                requestAnimationFrame(this.flashHighScore.bind(this));
        },

        /**
         * Draw empty rectangle over high score.
         */
        clearHighScoreBounds() {
            this.canvasCtx.save();
            this.canvasCtx.fillStyle = '#fff';
            this.canvasCtx.rect(this.highScoreBounds.x, this.highScoreBounds.y,
                this.highScoreBounds.width, this.highScoreBounds.height);
            this.canvasCtx.fill();
            this.canvasCtx.restore();
        },

        /**
         * Starts the flashing of the high score.
         */
        startHighScoreFlashing() {
            this.highScoreFlashing = true;
            this.flashHighScore();
        },

        /**
         * Whether high score is flashing.
         * @return {boolean}
         */
        isHighScoreFlashing() {
            return this.highScoreFlashing;
        },

        /**
         * Stop flashing the high score.
         */
        cancelHighScoreFlashing() {
            if (this.flashingRafId) {
                cancelAnimationFrame(this.flashingRafId);
            }
            this.flashIterations = 0;
            this.flashTimer = 0;
            this.highScoreFlashing = false;
            this.clearHighScoreBounds();
            this.drawHighScore();
        },

        /**
         * Clear the high score.
         */
        resetHighScore() {
            this.setHighScore(0);
            this.cancelHighScoreFlashing();
        },

        /**
         * Reset the distance meter back to '00000'.
         */
        reset() {
            this.update(0, 0);
            this.achievement = false;
        }
    };


    //******************************************************************************

    /**
     * Cloud background item.
     * Similar to an obstacle object but without collision boxes.
     * @param {HTMLCanvasElement} canvas Canvas element.
     * @param {Object} spritePos Position of image in sprite.
     * @param {number} containerWidth
     * @constructor
     */
    function Cloud(canvas, spritePos, containerWidth) {
        this.canvas = canvas;
        this.canvasCtx =
            /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d'));
        this.spritePos = spritePos;
        this.containerWidth = containerWidth;
        this.xPos = containerWidth;
        this.yPos = 0;
        this.remove = false;
        this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP,
            Cloud.config.MAX_CLOUD_GAP);

        this.init();
    }


    /**
     * Cloud object config.
     * @enum {number}
     */
    Cloud.config = {
        HEIGHT: 14,
        MAX_CLOUD_GAP: 400,
        MAX_SKY_LEVEL: 30,
        MIN_CLOUD_GAP: 100,
        MIN_SKY_LEVEL: 71,
        WIDTH: 46
    };


    Cloud.prototype = {
        /**
         * Initialise the cloud. Sets the Cloud height.
         */
        init() {
            this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,
                Cloud.config.MIN_SKY_LEVEL);
            this.draw();
        },

        /**
         * Draw the cloud.
         */
        draw() {
            this.canvasCtx.save();
            let sourceWidth = Cloud.config.WIDTH;
            let sourceHeight = Cloud.config.HEIGHT;
            const outputWidth = sourceWidth;
            const outputHeight = sourceHeight;
            if (IS_HIDPI) {
                sourceWidth = sourceWidth * 2;
                sourceHeight = sourceHeight * 2;
            }

            this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x,
                this.spritePos.y,
                sourceWidth, sourceHeight,
                this.xPos, this.yPos,
                outputWidth, outputHeight);

            this.canvasCtx.restore();
        },

        /**
         * Update the cloud position.
         * @param {number} speed
         */
        update(speed) {
            if (!this.remove) {
                this.xPos -= Math.ceil(speed);
                this.draw();

                // Mark as removeable if no longer in the canvas.
                if (!this.isVisible()) {
                    this.remove = true;
                }
            }
        },

        /**
         * Check if the cloud is visible on the stage.
         * @return {boolean}
         */
        isVisible() {
            return this.xPos + Cloud.config.WIDTH > 0;
        }
    };


    //******************************************************************************

    /**
     * Nightmode shows a moon and stars on the horizon.
     * @param {HTMLCanvasElement} canvas
     * @param {number} spritePos
     * @param {number} containerWidth
     * @constructor
     */
    function NightMode(canvas, spritePos, containerWidth) {
        this.spritePos = spritePos;
        this.canvas = canvas;
        this.canvasCtx =
            /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
        this.xPos = containerWidth - 50;
        this.yPos = 30;
        this.currentPhase = 0;
        this.opacity = 0;
        this.containerWidth = containerWidth;
        this.stars = [];
        this.drawStars = false;
        this.placeStars();
    }

    /**
     * @enum {number}
     */
    NightMode.config = {
        FADE_SPEED: 0.035,
        HEIGHT: 40,
        MOON_SPEED: 0.25,
        NUM_STARS: 2,
        STAR_SIZE: 9,
        STAR_SPEED: 0.3,
        STAR_MAX_Y: 70,
        WIDTH: 20
    };

    NightMode.phases = [140, 120, 100, 60, 40, 20, 0];

    NightMode.prototype = {
        /**
         * Update moving moon, changing phases.
         * @param {boolean} activated Whether night mode is activated.
         */
        update(activated) {
            // Moon phase.
            if (activated && this.opacity === 0) {
                this.currentPhase++;

                if (this.currentPhase >= NightMode.phases.length) {
                    this.currentPhase = 0;
                }
            }

            // Fade in / out.
            if (activated && (this.opacity < 1 || this.opacity === 0)) {
                this.opacity += NightMode.config.FADE_SPEED;
            } else if (this.opacity > 0) {
                this.opacity -= NightMode.config.FADE_SPEED;
            }

            // Set moon positioning.
            if (this.opacity > 0) {
                this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);

                // Update stars.
                if (this.drawStars) {
                    for (let i = 0; i < NightMode.config.NUM_STARS; i++) {
                        this.stars[i].x =
                            this.updateXPos(this.stars[i].x, NightMode.config.STAR_SPEED);
                    }
                }
                this.draw();
            } else {
                this.opacity = 0;
                this.placeStars();
            }
            this.drawStars = true;
        },

        updateXPos(currentPos, speed) {
            if (currentPos < -NightMode.config.WIDTH) {
                currentPos = this.containerWidth;
            } else {
                currentPos -= speed;
            }
            return currentPos;
        },

        draw() {
            let moonSourceWidth = this.currentPhase === 3 ? NightMode.config.WIDTH * 2 :
                NightMode.config.WIDTH;
            let moonSourceHeight = NightMode.config.HEIGHT;
            let moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase];
            const moonOutputWidth = moonSourceWidth;
            let starSize = NightMode.config.STAR_SIZE;
            let starSourceX = Runner.spriteDefinition.LDPI.STAR.x;

            if (IS_HIDPI) {
                moonSourceWidth *= 2;
                moonSourceHeight *= 2;
                moonSourceX = this.spritePos.x +
                    (NightMode.phases[this.currentPhase] * 2);
                starSize *= 2;
                starSourceX = Runner.spriteDefinition.HDPI.STAR.x;
            }

            this.canvasCtx.save();
            this.canvasCtx.globalAlpha = this.opacity;

            // Stars.
            if (this.drawStars) {
                for (let i = 0; i < NightMode.config.NUM_STARS; i++) {
                    this.canvasCtx.drawImage(Runner.imageSprite,
                        starSourceX, this.stars[i].sourceY, starSize, starSize,
                        Math.round(this.stars[i].x), this.stars[i].y,
                        NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE);
                }
            }

            // Moon.
            this.canvasCtx.drawImage(Runner.imageSprite, moonSourceX,
                this.spritePos.y, moonSourceWidth, moonSourceHeight,
                Math.round(this.xPos), this.yPos,
                moonOutputWidth, NightMode.config.HEIGHT);

            this.canvasCtx.globalAlpha = 1;
            this.canvasCtx.restore();
        },

        // Do star placement.
        placeStars() {
            const segmentSize = Math.round(this.containerWidth /
                NightMode.config.NUM_STARS);

            for (let i = 0; i < NightMode.config.NUM_STARS; i++) {
                this.stars[i] = {};
                this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1));
                this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y);

                if (IS_HIDPI) {
                    this.stars[i].sourceY = Runner.spriteDefinition.HDPI.STAR.y +
                        NightMode.config.STAR_SIZE * 2 * i;
                } else {
                    this.stars[i].sourceY = Runner.spriteDefinition.LDPI.STAR.y +
                        NightMode.config.STAR_SIZE * i;
                }
            }
        },

        reset() {
            this.currentPhase = 0;
            this.opacity = 0;
            this.update(false);
        }

    };


    //******************************************************************************

    /**
     * Horizon Line.
     * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
     * @param {HTMLCanvasElement} canvas
     * @param {Object} spritePos Horizon position in sprite.
     * @constructor
     */
    function HorizonLine(canvas, spritePos) {
        this.spritePos = spritePos;
        this.canvas = canvas;
        this.canvasCtx =
            /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
        this.sourceDimensions = {};
        this.dimensions = HorizonLine.dimensions;
        this.sourceXPos = [this.spritePos.x, this.spritePos.x +
        this.dimensions.WIDTH];
        this.xPos = [];
        this.yPos = 0;
        this.bumpThreshold = 0.5;

        this.setSourceDimensions();
        this.draw();
    }


    /**
     * Horizon line dimensions.
     * @enum {number}
     */
    HorizonLine.dimensions = {
        WIDTH: 600,
        HEIGHT: 12,
        YPOS: 127
    };


    HorizonLine.prototype = {
        /**
         * Set the source dimensions of the horizon line.
         */
        setSourceDimensions() {
            for (const dimension in HorizonLine.dimensions) {
                if (IS_HIDPI) {
                    if (dimension !== 'YPOS') {
                        this.sourceDimensions[dimension] =
                            HorizonLine.dimensions[dimension] * 2;
                    }
                } else {
                    this.sourceDimensions[dimension] =
                        HorizonLine.dimensions[dimension];
                }
                this.dimensions[dimension] = HorizonLine.dimensions[dimension];
            }

            this.xPos = [0, HorizonLine.dimensions.WIDTH];
            this.yPos = HorizonLine.dimensions.YPOS;
        },

        /**
         * Return the crop x position of a type.
         */
        getRandomType() {
            return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
        },

        /**
         * Draw the horizon line.
         */
        draw() {
            this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0],
                this.spritePos.y,
                this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
                this.xPos[0], this.yPos,
                this.dimensions.WIDTH, this.dimensions.HEIGHT);

            this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1],
                this.spritePos.y,
                this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
                this.xPos[1], this.yPos,
                this.dimensions.WIDTH, this.dimensions.HEIGHT);
        },

        /**
         * Update the x position of an indivdual piece of the line.
         * @param {number} pos Line position.
         * @param {number} increment
         */
        updateXPos(pos, increment) {
            const line1 = pos;
            const line2 = pos === 0 ? 1 : 0;

            this.xPos[line1] -= increment;
            this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;

            if (this.xPos[line1] <= -this.dimensions.WIDTH) {
                this.xPos[line1] += this.dimensions.WIDTH * 2;
                this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
                this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;
            }
        },

        /**
         * Update the horizon line.
         * @param {number} deltaTime
         * @param {number} speed
         */
        update(deltaTime, speed) {
            const increment = Math.floor(speed * (FPS / 1000) * deltaTime);

            if (this.xPos[0] <= 0) {
                this.updateXPos(0, increment);
            } else {
                this.updateXPos(1, increment);
            }
            this.draw();
        },

        /**
         * Reset horizon to the starting position.
         */
        reset() {
            this.xPos[0] = 0;
            this.xPos[1] = HorizonLine.dimensions.WIDTH;
        }
    };


    //******************************************************************************

    /**
     * Horizon background class.
     * @param {HTMLCanvasElement} canvas
     * @param {Object} spritePos Sprite positioning.
     * @param {Object} dimensions Canvas dimensions.
     * @param {number} gapCoefficient
     * @constructor
     */
    function Horizon(canvas, spritePos, dimensions, gapCoefficient) {
        this.canvas = canvas;
        this.canvasCtx =
            /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d'));
        this.config = Horizon.config;
        this.dimensions = dimensions;
        this.gapCoefficient = gapCoefficient;
        this.obstacles = [];
        this.obstacleHistory = [];
        this.horizonOffsets = [0, 0];
        this.cloudFrequency = this.config.CLOUD_FREQUENCY;
        this.spritePos = spritePos;
        this.nightMode = null;

        // Cloud
        this.clouds = [];
        this.cloudSpeed = this.config.BG_CLOUD_SPEED;

        // Horizon
        this.horizonLine = null;
        this.init();
    }


    /**
     * Horizon config.
     * @enum {number}
     */
    Horizon.config = {
        BG_CLOUD_SPEED: 0.2,
        BUMPY_THRESHOLD: .3,
        CLOUD_FREQUENCY: .5,
        HORIZON_HEIGHT: 16,
        MAX_CLOUDS: 6
    };


    Horizon.prototype = {
        /**
         * Initialise the horizon. Just add the line and a cloud. No obstacles.
         */
        init() {
            this.addCloud();
            this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON);
            this.nightMode = new NightMode(this.canvas, this.spritePos.MOON,
                this.dimensions.WIDTH);
        },

        /**
         * @param {number} deltaTime
         * @param {number} currentSpeed
         * @param {boolean} updateObstacles Used as an override to prevent
         *     the obstacles from being updated / added. This happens in the
         *     ease in section.
         * @param {boolean} showNightMode Night mode activated.
         */
        update(deltaTime, currentSpeed, updateObstacles, showNightMode) {
            this.runningTime += deltaTime;
            this.horizonLine.update(deltaTime, currentSpeed);
            this.nightMode.update(showNightMode);
            this.updateClouds(deltaTime, currentSpeed);

            if (updateObstacles) {
                this.updateObstacles(deltaTime, currentSpeed);
            }
        },

        /**
         * Update the cloud positions.
         * @param {number} deltaTime
         * @param {number} speed
         */
        updateClouds(deltaTime, speed) {
            const cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed;
            const numClouds = this.clouds.length;

            if (numClouds) {
                for (let i = numClouds - 1; i >= 0; i--) {
                    this.clouds[i].update(cloudSpeed);
                }

                const lastCloud = this.clouds[numClouds - 1];

                // Check for adding a new cloud.
                if (numClouds < this.config.MAX_CLOUDS &&
                    (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap &&
                    this.cloudFrequency > Math.random()) {
                    this.addCloud();
                }

                // Remove expired clouds.
                this.clouds = this.clouds.filter(function(obj) {
                    return !obj.remove;
                });
            } else {
                this.addCloud();
            }
        },

        /**
         * Update the obstacle positions.
         * @param {number} deltaTime
         * @param {number} currentSpeed
         */
        updateObstacles(deltaTime, currentSpeed) {
            // Obstacles, move to Horizon layer.
            const updatedObstacles = this.obstacles.slice(0);

            for (let i = 0; i < this.obstacles.length; i++) {
                const obstacle = this.obstacles[i];
                obstacle.update(deltaTime, currentSpeed);

                // Clean up existing obstacles.
                if (obstacle.remove) {
                    updatedObstacles.shift();
                }
            }
            this.obstacles = updatedObstacles;

            if (this.obstacles.length > 0) {
                const lastObstacle = this.obstacles[this.obstacles.length - 1];

                if (lastObstacle && !lastObstacle.followingObstacleCreated &&
                    lastObstacle.isVisible() &&
                    (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
                    this.dimensions.WIDTH) {
                    this.addNewObstacle(currentSpeed);
                    lastObstacle.followingObstacleCreated = true;
                }
            } else {
                // Create new obstacles.
                this.addNewObstacle(currentSpeed);
            }
        },

        removeFirstObstacle() {
            this.obstacles.shift();
        },

        /**
         * Add a new obstacle.
         * @param {number} currentSpeed
         */
        addNewObstacle(currentSpeed) {
            const obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1);
            const obstacleType = Obstacle.types[obstacleTypeIndex];

            // Check for multiples of the same type of obstacle.
            // Also check obstacle is available at current speed.
            if (this.duplicateObstacleCheck(obstacleType.type) ||
                currentSpeed < obstacleType.minSpeed) {
                this.addNewObstacle(currentSpeed);
            } else {
                const obstacleSpritePos = this.spritePos[obstacleType.type];

                this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType,
                    obstacleSpritePos, this.dimensions,
                    this.gapCoefficient, currentSpeed, obstacleType.width));

                this.obstacleHistory.unshift(obstacleType.type);

                if (this.obstacleHistory.length > 1) {
                    this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION);
                }
            }
        },

        /**
         * Returns whether the previous two obstacles are the same as the next one.
         * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION.
         * @return {boolean}
         */
        duplicateObstacleCheck(nextObstacleType) {
            let duplicateCount = 0;

            for (let i = 0; i < this.obstacleHistory.length; i++) {
                duplicateCount =
                    this.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0;
            }
            return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION;
        },

        /**
         * Reset the horizon layer.
         * Remove existing obstacles and reposition the horizon line.
         */
        reset() {
            this.obstacles = [];
            this.horizonLine.reset();
            this.nightMode.reset();
        },

        /**
         * Update the canvas width and scaling.
         * @param {number} width Canvas width.
         * @param {number} height Canvas height.
         */
        resize(width, height) {
            this.canvas.width = width;
            this.canvas.height = height;
        },

        /**
         * Add a new cloud to the horizon.
         */
        addCloud() {
            this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD,
                this.dimensions.WIDTH));
        }
    };
    </script>
</head>
<body id="t" style="font-family: sans, Arial, sans-serif; font-size: 75%" jstcache="0" class="neterror">
<div id="main-frame-error" class="interstitial-wrapper" jstcache="0">
    <div id="main-content" jstcache="0">
        <div class="icon icon-generic" jseval="updateIconClass(this.classList, iconClass)" alt="" jstcache="1"></div>
        <div id="main-message" jstcache="0">
            <h1 jstcache="0">
                <span jsselect="heading" jsvalues=".innerHTML:msg" jstcache="10">This site can’t be reached</span>
                <a id="error-information-button" class="hidden" onclick="toggleErrorInformationPopup();" jstcache="0"></a>
            </h1>
            <p jsselect="summary" jsvalues=".innerHTML:msg" jstcache="2"><strong jscontent="hostName" jstcache="23">drulber.ru</strong> refused to connect.</p>
            <!--The suggestion list and error code are normally presented inline,
          in which case error-information-popup-* divs have no effect. When
          error-information-popup-container has the use-popup-container class, this
          information is provided in a popup instead.-->
            <div id="error-information-popup-container" jstcache="0">
                <div id="error-information-popup" jstcache="0">
                    <div id="error-information-popup-box" jstcache="0">
                        <div id="error-information-popup-content" jstcache="0">
                            <div id="suggestions-list" style="" jsdisplay="(suggestionsSummaryList &amp;&amp; suggestionsSummaryList.length)" jstcache="17">
                                <p jsvalues=".innerHTML:suggestionsSummaryListHeader" jstcache="19">Try:</p>
                                <ul jsvalues=".className:suggestionsSummaryList.length == 1 ? 'single-suggestion' : ''" jstcache="20" class="">
                                    <li jsselect="suggestionsSummaryList" jsvalues=".innerHTML:summary" jstcache="22" jsinstance="0">Checking the connection</li><li jsselect="suggestionsSummaryList" jsvalues=".innerHTML:summary" jstcache="22" jsinstance="*1"><a href="#buttons" onclick="toggleHelpBox()" jstcache="0">Checking the proxy and the firewall</a></li>
                                </ul>
                            </div>
                            <div class="error-code" jscontent="errorCode" jstcache="18">ERR_CONNECTION_REFUSED</div>
                            <p id="error-information-popup-close" jstcache="0">
                                <a class="link-button" jscontent="closeDescriptionPopup" onclick="toggleErrorInformationPopup();" jstcache="21">null</a>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
            <div id="diagnose-frame" class="hidden" jstcache="0"></div>
            <div id="download-links-wrapper" class="hidden" jstcache="0">
                <div id="download-link-wrapper" jstcache="0">
                    <a id="download-link" class="link-button" onclick="downloadButtonClick()" jsselect="downloadButton" jscontent="msg" jsvalues=".disabledText:disabledMsg" jstcache="7" style="display: none;">
                    </a>
                </div>
                <div id="download-link-clicked-wrapper" class="hidden" jstcache="0">
                    <div id="download-link-clicked" class="link-button" jsselect="downloadButton" jscontent="disabledMsg" jstcache="12" style="display: none;">
                    </div>
                </div>
            </div>
            <div id="save-page-for-later-button" class="hidden" jstcache="0">
                <a class="link-button" onclick="savePageLaterClick()" jsselect="savePageLater" jscontent="savePageMsg" jstcache="11" style="display: none;">
                </a>
            </div>
            <div id="cancel-save-page-button" class="hidden" onclick="cancelSavePageClick()" jsselect="savePageLater" jsvalues=".innerHTML:cancelMsg" jstcache="5" style="display: none;">
            </div>
            <div id="offline-content-list" class="list-hidden" hidden="" jstcache="0">
                <div id="offline-content-list-visibility-card" onclick="toggleOfflineContentListVisibility(true)" jstcache="0">
                    <div id="offline-content-list-title" jsselect="offlineContentList" jscontent="title" jstcache="13" style="display: none;">
                    </div>
                    <div jstcache="0">
                        <div id="offline-content-list-show-text" jsselect="offlineContentList" jscontent="showText" jstcache="15" style="display: none;">
                        </div>
                        <div id="offline-content-list-hide-text" jsselect="offlineContentList" jscontent="hideText" jstcache="16" style="display: none;">
                        </div>
                    </div>
                </div>
                <div id="offline-content-suggestions" jstcache="0"></div>
                <div id="offline-content-list-action" jstcache="0">
                    <a class="link-button" onclick="launchDownloadsPage()" jsselect="offlineContentList" jscontent="actionText" jstcache="14" style="display: none;">
                    </a>
                </div>
            </div>
        </div>
    </div>
    <div id="buttons" class="nav-wrapper suggested-right" jstcache="0">
        <div id="control-buttons" jstcache="0">
            <button id="reload-button" class="blue-button text-button" onclick="trackClick(this.trackingId);
                     reloadButtonClick(this.url);" jsselect="reloadButton" jsvalues=".url:reloadUrl; .trackingId:reloadTrackingId" jscontent="msg" jstcache="6">Reload</button>
            <button id="download-button" class="blue-button text-button" onclick="downloadButtonClick()" jsselect="downloadButton" jscontent="msg" jsvalues=".disabledText:disabledMsg" jstcache="7" style="display: none;">
            </button>
        </div>
        <button id="details-button" class="secondary-button text-button small-link" onclick="detailsButtonClick(); toggleHelpBox()" jscontent="details" jsdisplay="(suggestionsDetails &amp;&amp; suggestionsDetails.length > 0) || diagnose" jsvalues=".detailsText:details; .hideDetailsText:hideDetails;" jstcache="3">Details</button>
    </div>
    <div id="details" class="hidden" jstcache="0">
        <div class="suggestions" jsselect="suggestionsDetails" jstcache="4" jsinstance="0">
            <div class="suggestion-header" jsvalues=".innerHTML:header" jstcache="8">Check your Internet connection</div>
            <div class="suggestion-body" jsvalues=".innerHTML:body" jstcache="9">Check any cables and reboot any routers, modems, or other network
                devices you may be using.</div>
        </div><div class="suggestions" jsselect="suggestionsDetails" jstcache="4" jsinstance="1">
        <div class="suggestion-header" jsvalues=".innerHTML:header" jstcache="8">Allow browser to access the network in your firewall or antivirus
            settings.</div>
        <div class="suggestion-body" jsvalues=".innerHTML:body" jstcache="9">If it is already listed as a program allowed to access the network, try
            removing it from the list and adding it again.</div>
    </div><div class="suggestions" jsselect="suggestionsDetails" jstcache="4" jsinstance="*2">
        <div class="suggestion-header" jsvalues=".innerHTML:header" jstcache="8">If you use a proxy server…</div>
        <div class="suggestion-body" jsvalues=".innerHTML:body" jstcache="9">Check your proxy settings or contact your network administrator to
            make sure the proxy server is working. If you don't believe you should
            be using a proxy server:
            Go to
            the menu &gt;
            <span jscontent="settingsTitle" jstcache="24">Settings</span>
            &gt;
            <span jscontent="advancedTitle" jstcache="25">Show advanced settings…</span>
            &gt;
            <span jscontent="proxyTitle" jstcache="26">Change proxy settings…</span>
            and make sure your configuration is set to "no proxy" or "direct."</div>
    </div>
    </div>
</div>
<div id="sub-frame-error" jstcache="0">
    <!-- Show details when hovering over the icon, in case the details are
         hidden because they're too large. -->
    <div class="icon icon-generic" jseval="updateIconClass(this.classList, iconClass)" jstcache="1"></div>
    <div id="sub-frame-error-details" jsselect="summary" jsvalues=".innerHTML:msg" jstcache="2"><strong jscontent="hostName" jstcache="23">drulber.ru</strong> refused to connect.</div>
</div>

<div id="offline-resources" jstcache="0">
    <img id="offline-resources-1x" src="data:image/png;base64,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" jstcache="0">
    <img id="offline-resources-2x" src="data:image/png;base64,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" jstcache="0">
    <template id="audio-resources" jstcache="0">
        <audio id="offline-sound-press" src="data:audio/mpeg;base64,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"></audio>
        <audio id="offline-sound-hit" src="data:audio/mpeg;base64,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"></audio>
        <audio id="offline-sound-reached" src="data:audio/mpeg;base64,T2dnUwACAAAAAAAAAABVDxppAAAAABYzHfUBHgF2b3JiaXMAAAAAAkSsAAD/////AHcBAP////+4AU9nZ1MAAAAAAAAAAAAAVQ8aaQEAAAC9PVXbEEf//////////////////+IDdm9yYmlzNwAAAEFPOyBhb1R1ViBiNSBbMjAwNjEwMjRdIChiYXNlZCBvbiBYaXBoLk9yZydzIGxpYlZvcmJpcykAAAAAAQV2b3JiaXMlQkNWAQBAAAAkcxgqRqVzFoQQGkJQGeMcQs5r7BlCTBGCHDJMW8slc5AhpKBCiFsogdCQVQAAQAAAh0F4FISKQQghhCU9WJKDJz0IIYSIOXgUhGlBCCGEEEIIIYQQQgghhEU5aJKDJ0EIHYTjMDgMg+U4+ByERTlYEIMnQegghA9CuJqDrDkIIYQkNUhQgwY56ByEwiwoioLEMLgWhAQ1KIyC5DDI1IMLQoiag0k1+BqEZ0F4FoRpQQghhCRBSJCDBkHIGIRGQViSgwY5uBSEy0GoGoQqOQgfhCA0ZBUAkAAAoKIoiqIoChAasgoAyAAAEEBRFMdxHMmRHMmxHAsIDVkFAAABAAgAAKBIiqRIjuRIkiRZkiVZkiVZkuaJqizLsizLsizLMhAasgoASAAAUFEMRXEUBwgNWQUAZAAACKA4iqVYiqVoiueIjgiEhqwCAIAAAAQAABA0Q1M8R5REz1RV17Zt27Zt27Zt27Zt27ZtW5ZlGQgNWQUAQAAAENJpZqkGiDADGQZCQ1YBAAgAAIARijDEgNCQVQAAQAAAgBhKDqIJrTnfnOOgWQ6aSrE5HZxItXmSm4q5Oeecc87J5pwxzjnnnKKcWQyaCa0555zEoFkKmgmtOeecJ7F50JoqrTnnnHHO6WCcEcY555wmrXmQmo21OeecBa1pjppLsTnnnEi5eVKbS7U555xzzjnnnHPOOeec6sXpHJwTzjnnnKi9uZab0MU555xPxunenBDOOeecc84555xzzjnnnCA0ZBUAAAQAQBCGjWHcKQjS52ggRhFiGjLpQffoMAkag5xC6tHoaKSUOggllXFSSicIDVkFAAACAEAIIYUUUkghhRRSSCGFFGKIIYYYcsopp6CCSiqpqKKMMssss8wyyyyzzDrsrLMOOwwxxBBDK63EUlNtNdZYa+4555qDtFZaa621UkoppZRSCkJDVgEAIAAABEIGGWSQUUghhRRiiCmnnHIKKqiA0JBVAAAgAIAAAAAAT/Ic0REd0REd0REd0REd0fEczxElURIlURIt0zI101NFVXVl15Z1Wbd9W9iFXfd93fd93fh1YViWZVmWZVmWZVmWZVmWZVmWIDRkFQAAAgAAIIQQQkghhRRSSCnGGHPMOegklBAIDVkFAAACAAgAAABwFEdxHMmRHEmyJEvSJM3SLE/zNE8TPVEURdM0VdEVXVE3bVE2ZdM1XVM2XVVWbVeWbVu2dduXZdv3fd/3fd/3fd/3fd/3fV0HQkNWAQASAAA6kiMpkiIpkuM4jiRJQGjIKgBABgBAAACK4iiO4ziSJEmSJWmSZ3mWqJma6ZmeKqpAaMgqAAAQAEAAAAAAAACKpniKqXiKqHiO6IiSaJmWqKmaK8qm7Lqu67qu67qu67qu67qu67qu67qu67qu67qu67qu67qu67quC4SGrAIAJAAAdCRHciRHUiRFUiRHcoDQkFUAgAwAgAAAHMMxJEVyLMvSNE/zNE8TPdETPdNTRVd0gdCQVQAAIACAAAAAAAAADMmwFMvRHE0SJdVSLVVTLdVSRdVTVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTdM0TRMIDVkJAJABAKAQW0utxdwJahxi0nLMJHROYhCqsQgiR7W3yjGlHMWeGoiUURJ7qihjiknMMbTQKSet1lI6hRSkmFMKFVIOWiA0ZIUAEJoB4HAcQLIsQLI0AAAAAAAAAJA0DdA8D7A8DwAAAAAAAAAkTQMsTwM0zwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQNI0QPM8QPM8AAAAAAAAANA8D/BEEfBEEQAAAAAAAAAszwM80QM8UQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwNE0QPM8QPM8AAAAAAAAALA8D/BEEfA8EQAAAAAAAAA0zwM8UQQ8UQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABDgAAAQYCEUGrIiAIgTADA4DjQNmgbPAziWBc+D50EUAY5lwfPgeRBFAAAAAAAAAAAAADTPg6pCVeGqAM3zYKpQVaguAAAAAAAAAAAAAJbnQVWhqnBdgOV5MFWYKlQVAAAAAAAAAAAAAE8UobpQXbgqwDNFuCpcFaoLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAABhwAAAIMKEMFBqyIgCIEwBwOIplAQCA4ziWBQAAjuNYFgAAWJYligAAYFmaKAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAGHAAAAgwoQwUGrISAIgCADAoimUBy7IsYFmWBTTNsgCWBtA8gOcBRBEACAAAKHAAAAiwQVNicYBCQ1YCAFEAAAZFsSxNE0WapmmaJoo0TdM0TRR5nqZ5nmlC0zzPNCGKnmeaEEXPM02YpiiqKhBFVRUAAFDgAAAQYIOmxOIAhYasBABCAgAMjmJZnieKoiiKpqmqNE3TPE8URdE0VdVVaZqmeZ4oiqJpqqrq8jxNE0XTFEXTVFXXhaaJommaommqquvC80TRNE1TVVXVdeF5omiapqmqruu6EEVRNE3TVFXXdV0giqZpmqrqurIMRNE0VVVVXVeWgSiapqqqquvKMjBN01RV15VdWQaYpqq6rizLMkBVXdd1ZVm2Aarquq4ry7INcF3XlWVZtm0ArivLsmzbAgAADhwAAAKMoJOMKouw0YQLD0ChISsCgCgAAMAYphRTyjAmIaQQGsYkhBJCJiWVlEqqIKRSUikVhFRSKiWjklJqKVUQUikplQpCKqWVVAAA2IEDANiBhVBoyEoAIA8AgCBGKcYYYwwyphRjzjkHlVKKMeeck4wxxphzzkkpGWPMOeeklIw555xzUkrmnHPOOSmlc84555yUUkrnnHNOSiklhM45J6WU0jnnnBMAAFTgAAAQYKPI5gQjQYWGrAQAUgEADI5jWZqmaZ4nipYkaZrneZ4omqZmSZrmeZ4niqbJ8zxPFEXRNFWV53meKIqiaaoq1xVF0zRNVVVVsiyKpmmaquq6ME3TVFXXdWWYpmmqquu6LmzbVFXVdWUZtq2aqiq7sgxcV3Vl17aB67qu7Nq2AADwBAcAoAIbVkc4KRoLLDRkJQCQAQBAGIOMQgghhRBCCiGElFIICQAAGHAAAAgwoQwUGrISAEgFAACQsdZaa6211kBHKaWUUkqpcIxSSimllFJKKaWUUkoppZRKSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoFAC5VOADoPtiwOsJJ0VhgoSErAYBUAADAGKWYck5CKRVCjDkmIaUWK4QYc05KSjEWzzkHoZTWWiyecw5CKa3FWFTqnJSUWoqtqBQyKSml1mIQwpSUWmultSCEKqnEllprQQhdU2opltiCELa2klKMMQbhg4+xlVhqDD74IFsrMdVaAABmgwMARIINqyOcFI0FFhqyEgAICQAgjFGKMcYYc8455yRjjDHmnHMQQgihZIwx55xzDkIIIZTOOeeccxBCCCGEUkrHnHMOQgghhFBS6pxzEEIIoYQQSiqdcw5CCCGEUkpJpXMQQgihhFBCSSWl1DkIIYQQQikppZRCCCGEEkIoJaWUUgghhBBCKKGklFIKIYRSQgillJRSSimFEEoIpZSSUkkppRJKCSGEUlJJKaUUQggllFJKKimllEoJoYRSSimlpJRSSiGUUEIpBQAAHDgAAAQYQScZVRZhowkXHoBCQ1YCAGQAAJSyUkoorVVAIqUYpNpCR5mDFHOJLHMMWs2lYg4pBq2GyjGlGLQWMgiZUkxKCSV1TCknLcWYSuecpJhzjaVzEAAAAEEAgICQAAADBAUzAMDgAOFzEHQCBEcbAIAgRGaIRMNCcHhQCRARUwFAYoJCLgBUWFykXVxAlwEu6OKuAyEEIQhBLA6ggAQcnHDDE294wg1O0CkqdSAAAAAAAAwA8AAAkFwAERHRzGFkaGxwdHh8gISIjJAIAAAAAAAYAHwAACQlQERENHMYGRobHB0eHyAhIiMkAQCAAAIAAAAAIIAABAQEAAAAAAACAAAABARPZ2dTAABARwAAAAAAAFUPGmkCAAAAZa2xyCElHh4dHyQvOP8T5v8NOEo2/wPOytDN39XY2P8N/w2XhoCs0CKt8NEKLdIKH63ShlVlwuuiLze+3BjtjfZGe0lf6As9ggZstNJFphRUtpUuMqWgsqrasj2IhOA1F7LFMdFaWzkAtNBFpisIQgtdZLqCIKjqAAa9WePLkKr1MMG1FlwGtNJFTSkIcitd1JSCIKsCAQWISK0Cyzw147T1tAK00kVNKKjQVrqoCQUVqqr412m+VKtZf9h+TDaaztAAtNRFzVEQlJa6qDkKgiIrc2gtfES4nSQ1mlvfMxfX4+b2t7ICVNGwkKiiYSGxTQtK1YArN+DgTqdjMwyD1q8dL6RfOzXZ0yO+qkZ8+Ub81WP+DwNkWcJhvlmWcJjvSbUK/WVm3LgxClkyiuxpIFtS5Gwi5FBkj2DGWEyHYBiLcRJkWnQSZGbRGYGZAHr6vWVJAWGE5q724ldv/B8Kp5II3dPvLUsKCCM0d7UXv3rj/1A4lUTo+kCUtXqtWimLssjIyMioViORobCJAQLYFnpaAACCAKEWAMCiQGqMABAIUKknAFkUIGsBIBBAHYBtgAFksAFsEySQgQDWQ4J1AOpiVBUHd1FE1d2IGDfGAUzmKiiTyWQyuY6Lx/W4jgkQZQKioqKuqioAiIqKwagqCqKiogYxCgACCiKoAAAIqAuKAgAgjyeICQAAvAEXmQAAmYNhMgDAZD5MJqYzppPpZDqMwzg0TVU9epXf39/9xw5lBaCpqJiG3VOsht0wRd8FgAeoB8APKOABQFT23GY0GgoAolkyckajHgBoZEYujQY+230BUoD/uf31br/7qCHLXLWwIjMIz3ZfgBTgf25/vdvvPmrIMlctrMgMwiwCAAB4FgAAggAAAM8CAEAgkNG0DgCeBQCAIAAAmEUBynoASKANMIAMNoBtAAlkMAGoAzKQgDoAdQYAKOoEANFgAoAyKwAAGIOiAACVBACyAAAAFYMDAAAyxyMAAMBMfgQAAMi8GAAACDfoFQAAYHgxACA16QiK4CoWcTcVAADDdNpc7AAAgJun080DAAAwPTwxDQAAxYanm1UFAAAVD0MsAA4AyCUztwBwBgAyQOTMTZYA0AAiySW3Clar/eRUAb5fPDXA75e8QH//jkogHmq1n5wqwPeLpwb4/ZIX6O/fUQnEgwf9fr/f72dmZmoaRUREhMLTADSVgCAgVLKaCT0tAABk2AFgAyQgEEDTSABtQiSQwQDUARksYBtAAgm2AQSQYBtAAuYPOK5rchyPLxAABFej4O7uAIgYNUYVEBExbozBGHdVgEoCYGZmAceDI0mGmZlrwYDHkQQAiLhxo6oKSHJk/oBrZgYASI4XAwDAXMMnIQAA5DoyDAAACa8AAMDM5JPEZDIZhiFJoN33vj4X6N19v15gxH8fAE1ERMShbm5iBYCOAAMFgAzaZs3ITURECAAhInKTNbNtfQDQNnuWHBERFgBUVa4iDqyqXEUc+AKkZlkmZCoJgIOBBaubqwoZ2SDNgJlj5MgsMrIV44xgKjCFYTS36QRGQafwylRZAhMXr7IEJi7+AqQ+gajAim2S1W/71ACEi4sIxsXVkSNDQRkgzGp6eNgMJDO7kiVXcmStkCVL0Ry0MzMgzRklI2dLliQNEbkUVFvaCApWW9oICq7rpRlKs2MBn8eVJRlk5JARjONMdGSYZArDOA0ZeKHD6+KN9oZ5MBDTCO8bmrptBBLgcnnOcBmk/KMhS2lL6rYRSIDL5TnDZZDyj4YspS3eIOoN9Uq1KIsMpp1gsU0gm412AISQyICYRYmsFQCQwWIgwWRCABASGRDawAKYxcCAyYQFgLhB1Rg17iboGF6v1+fIcR2TyeR4PF7HdVzHdVzHcYXPbzIAQNTFuBoVBQAADJOL15WBhNcFAADAI9cAAAAAAJAEmIsMAOBlvdTLVcg4mTnJzBnTobzDfKPRaDSaI1IAnUyHhr6LALxFo5FmyZlL1kAU5lW+LIBGo9lym1OF5ikAOsyctGkK8fgfAfgPIQDAvBLgmVsGoM01lwRAvCwAHje0zTiA/oUDAOYAHqv9+AQC4gEDMJ/bIrXsH0Ggyh4rHKv9+AQC4gEDMJ/bIrXsH0Ggyh4rDPUsAADAogBCk3oCQBAAAABBAAAg6FkAANCzAAAgBELTAACGQAAoGoFBFoWoAQDaBPoBQ0KdAQAAAK7iqkAVAABQNixAoRoAAKgE4CAiAAAAACAYow6IGjcAAAAAAPL4DfZ6kkZkprlkj6ACu7i7u5sKAAAOd7vhAAAAAEBxt6m6CjSAgKrFasUOAAAoAABic/d0EwPIBjAA0CAggABojlxzLQD+mv34BQXEBQvYH5sijDr0/FvZOwu/Zj9+QQFxwQL2x6YIow49/1b2zsI9CwAAeBYAAIBANGlSDQAABAEAAKBnIQEAeloAABgCCU0AAEMgAGQTYNAG+gCwAeiBIWMAGmYAAICogRg16gAAABB1gwVkNlgAAIDIGnCMOwIAAACAgmPA8CpgBgAAAIDMG/QbII/PLwAAaKN9vl4Pd3G6maoAAAAAapiKaQUAANPTxdXhJkAWXHBzcRcFAAAHAABqNx2YEQAHHIADOAEAvpp9fyMBscACmc9Lku7s1RPB+kdWs+9vJCAWWCDzeUnSnb16Ilj/CNOzAACAZwEAAAhEk6ZVAAAIAgAAQc8CAICeFgAAhiAAABgCAUAjMGgDPQB6CgCikmDIGIDqCAAAkDUQdzUOAAAAKg3WIKsCAABkFkAJAAAAQFzFQXh8QQMAAAAABCMCKEhAAACAkXcOo6bDxCgqOMXV6SoKAAAAoGrabDYrAAAiHq5Ww80EBMiIi01tNgEAAAwAAKiHGGpRQADUKpgGAAAOEABogFFAAN6K/fghBIQ5cH0+roo0efVEquyBaMV+/BACwhy4Ph9XRZq8eiJV9kCQ9SwAAMCiAGhaDwAIAgAAIAgAAAQ9CwAAehYAAIQgAAAYAgGgaAAGWRTKBgBAG4AMADI2ANVFAAAAgKNqFKgGAACKRkpQqAEAgCKBAgAAAIAibkDFuDEAAAAAYODzA1iQoAEAAI3+ZYOMNls0AoEdN1dPiwIAgNNp2JwAAAAAYHgaLoa7QgNwgKeImAoAAA4AALU5XNxFoYFaVNxMAQCAjADAAQaeav34QgLiAQM4H1dNGbXoH8EIlT2SUKr14wsJiAcM4HxcNWXUon8EI1T2SEJMzwIAgJ4FAAAgCAAAhCAAABD0LAAA6GkBAEAIAgCAIRAAqvUAgywK2QgAyKIAoBEYAiGqCQB1BQAAqCNAmQEAAOqGFZANCwAAoBpQJgAAAKDiuIIqGAcAAAAA3Ig64LgoAADQHJ+WmYbJdMzQBsGuVk83mwIAAAIAgFNMV1cBUz1xKAAAgAEAwHR3sVldBRxAQD0d6uo0FAAADAAA6orNpqIAkMFqqMNAAQADKABkICgAfmr9+AUFxB0ANh+vita64VdPLCP9acKn1o9fUEDcAWDz8aporRt+9cQy0p8mjHsWAADwLAAAAEEAAAAEAQCAoGchAAD0LAAADIHQpAIADIEAUCsSDNpACwA2AK2EIaOVgLoCAACUBZCVAACAKBssIMqGFQAAoKoAjIMLAAAAAAgYIyB8BAUAAAAACPMJkN91ZAAA5O6kwzCtdAyIVd0cLi4KAAAAIFbD4uFiAbW5mu42AAAAAFBPwd1DoIEjgNNF7W4WQAEABwACODxdPcXIAAIHAEEBflr9/A0FxAULtD9eJWl006snRuXfq8Rp9fM3FBAXLND+eJWk0U2vnhiVf68STM8CAACeBQAAIAgAAIAgAAAQ9CwAAOhpAQBgCITGOgAwBAJAYwYYZFGoFgEAZFEAKCsBhkDIGgAoqwAAAFVAVCUAAKhU1aCIhgAAIMoacKNGVAEAAABwRBRQXEUUAAAAABUxCGAMRgAAAABNpWMnaZOWmGpxt7kAAAAAIBimq9pAbOLuYgMAAAAAww0300VBgAMRD0+HmAAAZAAAAKvdZsNUAAcoaAAgA04BXkr9+EIC4gQD2J/XRWjmV0/syr0xpdSPLyQgTjCA/XldhGZ+9cSu3BvD9CwAAOBZAAAAggAAAAgCgAQIehYAAPQsAAAIQQAAMAQCQJNMMMiiUDTNBABZFACyHmBIyCoAACAKoCIBACCLBjMhGxYAACCzAhQFAAAAYMBRFMUYAwAAAAAorg5gPZTJOI4yzhiM0hI1TZvhBgAAAIAY4mZxNcBQV1dXAAAAAAA3u4u7h4ICIYOni7u7qwGAAqAAAIhaHKI2ICCGXe2mAQBAgwwAAQIKQK6ZuREA/hm9dyCg9xrQforH3TSBf2dENdKfM5/RewcCeq8B7ad43E0T+HdGVCP9OWN6WgAA5CkANERJCAYAAIBgAADIAD0LAAB6WgAAmCBCUW8sAMAQCEBqWouAQRZFaigBgDaBSBgCIeoBAFkAwAiou6s4LqqIGgAAKMsKKKsCAAColIgbQV3ECAAACIBRQVzVjYhBVQEAAADJ55chBhUXEQEAIgmZOXNmTSNLthmTjNOZM8cMw2RIa9pdPRx2Q01VBZGNquHTq2oALBfQxKcAh/zVDReL4SEqIgBAbqcKYhiGgdXqblocygIAdL6s7qbaDKfdNE0FAQ4AVFVxeLi7W51DAgIAAwSWDoAPoHUAAt6YvDUqoHcE7If29ZNi2H/k+ir/85yQNiZvjQroHQH7oX39pBj2H7m+yv88J6QWi7cXgKFPJtNOABIEEGVEvUljJckAbdhetBOgpwFkZFbqtWqAUBgysL2AQR2gHoDYE3Dld12P18HkOuY1r+M4Hr/HAAAVBRejiCN4HE/QLOAGPJhMgAJi1BhXgwCAyZUCmOuHZuTMkTUia47sGdIs2TPajKwZqUiTNOKl/1fyvHS8fOn/1QGU+5U0SaOSzCxpmiNntsxI0LhZ+/0dmt1CVf8HNAXKl24AoM0D7jsIAMAASbPkmpvssuTMktIgALMAUESaJXuGzCyZQQBwgEZl5JqbnBlvgIyT0TAdSgG+6Px/rn+NclEGFGDR+f9c/xrlogwoAKjPiKKfIvRhGKYgzZLZbDkz2hC4djgeCVkXEKJlXz1uAosCujLkrDz6p0CZorVVOjvIQOAp3aVcLyCErGACSRKImCRMETeKzA6cFNd2X3KG1pyLgOnTDtnHXMSpVY1A6IXSjlNoh70ubc2VzXgfgd6uEQOBEmCt1O4wOHBQB2ANvtj8f65/jXKiAkiwWGz+P9e/RjlRASRYAODhfxqlH5QGhuxAobUGtOqEll3GqBEhYLIJQLMr6oQooHFcGpIsDK4yPg3UfMJtO/hTFVma3lrt+JI/EFBxbvlT2OiH0mhEfBofQDudLtq0lTiGSOKaVl6peD3XTDACuSXYNQAp4JoD7wjgUAC+2Px/rn+NcqIMKDBebP4/179GOVEGFBgDQPD/fxBW4I7k5DEgDtxdcwFpcNNx+JoDICRCTtO253ANTbn7DmF+TXalagLadQ23yhGw1Pj7SzpOajGmpeeYyqUY1/Y6KfuTVOU5cvu0gW2boGlMfFv5TejrOmkOl0iEpuQMpAYBB09nZ1MABINhAAAAAAAAVQ8aaQMAAAB/dp+bB5afkaKgrlp+2Px/rn+NchECSMBh8/+5/jXKRQggAQAI/tMRHf0LRqDj05brTRlASvIy1PwPFcajBhcoY0BtuEqvBZw0c0jJRaZ4n0f7fOKW0Y8QZ/M7xFeaGJktZ2ePGFTOLl4XzRCQMnJET4bVsFhMiiHf5vXtJ9vtMsf/Wzy030v3dqzCbkfN7af9JmpkTSXXICMpLAVO16AZoAF+2Px/rn91uQgGDOCw+f9c/+pyEQwYAACCH51SxFCg6SCEBi5Yzvla/iwJC4ekcPjs4PTWuY3tqJ0BKbo3cSYE4Oxo+TYjMXbYRhO+7lamNITiY2u0SUbFcZRMTaC5sUlWteBp+ZP4wUl9lzksq8hUQ5JOZZBAjfd98+8O6pvScEnEsrp/Z5BczwfWpkx5PwQ37EoIH7fMBgYGgusZAQN+2Px/rn91uQgGFOCw+f9c/+pyEQwoAPD/I8YfOD1cxsESTiLRCq0XjEpMtryCW+ZYCL2OrG5/pdkExMrQmjY9KVY4h4vfDR0No9dovrC2mxka1Pr0+Mu09SplWO6YXqWclpXdoVKuagQllrWfCaGA0R7bvLk41ZsRTBiieZFaqyFRFbasq0GwHT0MKbUIB2QAftj8f65/NbkIAQxwOGz+P9e/mlyEAAY4gEcfPYMyMh8UBxBogIAtTU0qrERaVBLhCkJQ3MmgzZNrxplCg6xVj5AdH8J2IE3bUNgyuD86evYivJmI+NREqmWbKqosI6xblSnNmJJUum+0qsMe4o8fIeCXELdErT52+KQtXSIl3XJNKOKv3BnKtS2cKmmnGpCqP/5YNQ9MCB2P8VUnCJiYDEAAXrj8f65/jXIiGJCAwuX/c/1rlBPBgAQA/ymlCDEi+hsNB2RoT865unFOQZiOpcy11YPQ6BiMettS0AZ0JqI4PV/Neludd25CqZDuiL82RhzdohJXt36nH+HlZiHE5ILqVSQL+T5/0h9qFzBVn0OFT9herDG3XzXz299VNY2RkejrK96EGyybKbXyG3IUUv5QEvq2bAP5CjJa9IiDeD5OOF64/H8uf3W5lAAmULj8fy5/dbmUACYAPEIfUcpgMGh0GgjCGlzQcHwGnb9HCrHg86LPrV1SbrhY+nX/N41X2DMb5NsNtkcRS9rs95w9uDtvP+KP/MupnfH3yHIbPG/1zDBygJimTvFcZywqne6OX18E1zluma5AShnVx4aqfxLo6K/C8P2fxH5cuaqtqE3Lbru4hT4283zc0Hqv2xINtisxZXBVfQuOAK6kCHjBAF6o/H+uf09ycQK6w6IA40Ll/3P9e5KLE9AdFgUYAwAAAgAAgDD4g+AgXAEEyAAEoADiPAAIcHGccHEAxN271+bn5+dt4B2YmGziAIrZMgZ4l2nedkACHggIAA=="></audio>
    </template>
</div>


<script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview This file defines a singleton which provides access to all data
 * that is available as soon as the page's resources are loaded (before DOM
 * content has finished loading). This data includes both localized strings and
 * any data that is important to have ready from a very early stage (e.g. things
 * that must be displayed right away).
 *
 * Note that loadTimeData is not guaranteed to be consistent between page
 * refreshes (https://crbug.com/740629) and should not contain values that might
 * change if the page is re-opened later.
 */

// #import {assert} from './assert.m.js';
// #import {parseHtmlSubset} from './parse_html_subset.m.js';

/**
 * @typedef {{
 *   substitutions: (Array<string>|undefined),
 *   attrs: (Object<function(Node, string):boolean>|undefined),
 *   tags: (Array<string>|undefined),
 * }}
 */
/* #export */ let SanitizeInnerHtmlOpts;

// eslint-disable-next-line no-var
/* #export */ /** @type {!LoadTimeData} */ var loadTimeData;

// Expose this type globally as a temporary work around until
// https://github.com/google/closure-compiler/issues/544 is fixed.
/** @constructor */
function LoadTimeData(){}

(function() {
    'use strict';

    LoadTimeData.prototype = {
        /**
         * Sets the backing object.
         *
         * Note that there is no getter for |data_| to discourage abuse of the form:
         *
         *     var value = loadTimeData.data()['key'];
         *
         * @param {Object} value The de-serialized page data.
         */
        set data(value) {
            expect(!this.data_, 'Re-setting data.');
            this.data_ = value;
        },

        /**
         * Returns a JsEvalContext for |data_|.
         * @returns {JsEvalContext}
         */
        createJsEvalContext() {
            return new JsEvalContext(this.data_);
        },

        /**
         * @param {string} id An ID of a value that might exist.
         * @return {boolean} True if |id| is a key in the dictionary.
         */
        valueExists(id) {
            return id in this.data_;
        },

        /**
         * Fetches a value, expecting that it exists.
         * @param {string} id The key that identifies the desired value.
         * @return {*} The corresponding value.
         */
        getValue(id) {
            expect(this.data_, 'No data. Did you remember to include strings.js?');
            const value = this.data_[id];
            expect(typeof value !== 'undefined', 'Could not find value for ' + id);
            return value;
        },

        /**
         * As above, but also makes sure that the value is a string.
         * @param {string} id The key that identifies the desired string.
         * @return {string} The corresponding string value.
         */
        getString(id) {
            const value = this.getValue(id);
            expectIsType(id, value, 'string');
            return /** @type {string} */ (value);
        },

        /**
         * Returns a formatted localized string where $1 to $9 are replaced by the
         * second to the tenth argument.
         * @param {string} id The ID of the string we want.
         * @param {...(string|number)} var_args The extra values to include in the
         *     formatted output.
         * @return {string} The formatted string.
         */
        getStringF(id, var_args) {
            const value = this.getString(id);
            if (!value) {
                return '';
            }

            const args = Array.prototype.slice.call(arguments);
            args[0] = value;
            return this.substituteString.apply(this, args);
        },

        /**
         * Make a string safe for use with with Polymer bindings that are
         * inner-h-t-m-l (or other innerHTML use).
         * @param {string} rawString The unsanitized string.
         * @param {SanitizeInnerHtmlOpts=} opts Optional additional allowed tags and
         *     attributes.
         * @return {string}
         */
        sanitizeInnerHtml(rawString, opts) {
            opts = opts || {};
            return parseHtmlSubset('<b>' + rawString + '</b>', opts.tags, opts.attrs)
                .firstChild.innerHTML;
        },

        /**
         * Returns a formatted localized string where $1 to $9 are replaced by the
         * second to the tenth argument. Any standalone $ signs must be escaped as
         * $$.
         * @param {string} label The label to substitute through.
         *     This is not an resource ID.
         * @param {...(string|number)} var_args The extra values to include in the
         *     formatted output.
         * @return {string} The formatted string.
         */
        substituteString(label, var_args) {
            const varArgs = arguments;
            return label.replace(/\$(.|$|\n)/g, function(m) {
                assert(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.');
                return m === '$$' ? '$' : varArgs[m[1]];
            });
        },

        /**
         * Returns a formatted string where $1 to $9 are replaced by the second to
         * tenth argument, split apart into a list of pieces describing how the
         * substitution was performed. Any standalone $ signs must be escaped as $$.
         * @param {string} label A localized string to substitute through.
         *     This is not an resource ID.
         * @param {...(string|number)} var_args The extra values to include in the
         *     formatted output.
         * @return {!Array<!{value: string, arg: (null|string)}>} The formatted
         *     string pieces.
         */
        getSubstitutedStringPieces(label, var_args) {
            const varArgs = arguments;
            // Split the string by separately matching all occurrences of $1-9 and of
            // non $1-9 pieces.
            const pieces = (label.match(/(\$[1-9])|(([^$]|\$([^1-9]|$))+)/g) ||
                []).map(function(p) {
                // Pieces that are not $1-9 should be returned after replacing $$
                // with $.
                if (!p.match(/^\$[1-9]$/)) {
                    assert(
                        (p.match(/\$/g) || []).length % 2 === 0,
                        'Unescaped $ found in localized string.');
                    return {value: p.replace(/\$\$/g, '$'), arg: null};
                }

                // Otherwise, return the substitution value.
                return {value: varArgs[p[1]], arg: p};
            });

            return pieces;
        },

        /**
         * As above, but also makes sure that the value is a boolean.
         * @param {string} id The key that identifies the desired boolean.
         * @return {boolean} The corresponding boolean value.
         */
        getBoolean(id) {
            const value = this.getValue(id);
            expectIsType(id, value, 'boolean');
            return /** @type {boolean} */ (value);
        },

        /**
         * As above, but also makes sure that the value is an integer.
         * @param {string} id The key that identifies the desired number.
         * @return {number} The corresponding number value.
         */
        getInteger(id) {
            const value = this.getValue(id);
            expectIsType(id, value, 'number');
            expect(value === Math.floor(value), 'Number isn\'t integer: ' + value);
            return /** @type {number} */ (value);
        },

        /**
         * Override values in loadTimeData with the values found in |replacements|.
         * @param {Object} replacements The dictionary object of keys to replace.
         */
        overrideValues(replacements) {
            expect(
                typeof replacements === 'object',
                'Replacements must be a dictionary object.');
            for (const key in replacements) {
                this.data_[key] = replacements[key];
            }
        }
    };

    /**
     * Checks condition, displays error message if expectation fails.
     * @param {*} condition The condition to check for truthiness.
     * @param {string} message The message to display if the check fails.
     */
    function expect(condition, message) {
        if (!condition) {
            console.error(
                'Unexpected condition on ' + document.location.href + ': ' + message);
        }
    }

    /**
     * Checks that the given value has the given type.
     * @param {string} id The id of the value (only used for error message).
     * @param {*} value The value to check the type on.
     * @param {string} type The type we expect |value| to be.
     */
    function expectIsType(id, value, type) {
        expect(
            typeof value === type, '[' + value + '] (' + id + ') is not a ' + type);
    }

    expect(!loadTimeData, 'should only include this file once');
    loadTimeData = new LoadTimeData;

    // Expose |loadTimeData| directly on |window|. This is only necessary by the
    // auto-generated load_time_data.m.js, since within a JS module the scope is
    // local.
    window.loadTimeData = loadTimeData;
})();
</script><script jstcache="0">loadTimeData.data = {"a11yenhanced":"","details":"Details","errorCode":"ERR_CONNECTION_REFUSED","fontfamily":"sans, Arial, sans-serif","fontsize":"75%","heading":{"hostName":"drulber.ru","msg":"This site can’t be reached"},"hideDetails":"Hide details","iconClass":"icon-generic","language":"en","reloadButton":{"msg":"Reload","reloadTrackingId":-1,"reloadUrl":"/rss.xml"},"suggestionsDetails":[{"body":"Check any cables and reboot any routers, modems, or other network\n    devices you may be using.","header":"Check your Internet connection"},{"body":"If it is already listed as a program allowed to access the network, try\n      removing it from the list and adding it again.","header":"Allow browser to access the network in your firewall or antivirus\n          settings."},{"advancedTitle":"Show advanced settings…","body":"Check your proxy settings or contact your network administrator to\n      make sure the proxy server is working. If you don't believe you should\n      be using a proxy server:\n      Go to\n          the browser menu >\n          \u003Cspan jscontent=\"settingsTitle\">\u003C/span>\n          >\n          \u003Cspan jscontent=\"advancedTitle\">\u003C/span>\n          >\n          \u003Cspan jscontent=\"proxyTitle\">\u003C/span>\n          and make sure your configuration is set to \"no proxy\" or \"direct.\"","header":"If you use a proxy server…","proxyTitle":"Change proxy settings…","settingsTitle":"Settings"}],"suggestionsSummaryList":[{"summary":"Checking the connection"},{"summary":"\u003Ca href=\"#buttons\" onclick=\"toggleHelpBox()\">Checking the proxy and the firewall\u003C/a>"}],"suggestionsSummaryListHeader":"Try:","summary":{"failedUrl":"/rss.xml","hostName":"drulber.ru","msg":"\u003Cstrong jscontent=\"hostName\">\u003C/strong> refused to connect."},"textdirection":"ltr","title":"drulber.ru"};</script><script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file serves as a proxy to bring the included js file from /third_party
// into its correct location under the resources directory tree, whence it is
// delivered via a browser://resources URL.  See ../webui_resources.grd.

// Note: this <include> is not behind a single-line comment because the first
// line of the file is source code (so the first line would be skipped) instead
// of a licence header.
// clang-format off

</script></body></html>
