[html]

<style>

#boston-tamagotchi {
    --bg: #17191f;
    --panel: #20242d;
    --panel2: #14171d;
    --border: #343a45;
    --text: #eeeeee;
    --muted: #979ca6;
    --accent: #9f2734;

    width: 100%;
    max-width: 620px;

    margin: 25px auto;
    padding: 22px;

    box-sizing: border-box;

    border-radius: 15px;

    background: var(--bg);

    color: var(--text);

    font-family:
        Arial,
        sans-serif;
}

#boston-tamagotchi *,
#boston-tamagotchi *::before,
#boston-tamagotchi *::after {
    box-sizing: border-box;
}

/* HEADER */

#boston-tamagotchi .tama-header {
    display: flex;

    justify-content: space-between;
    align-items: center;

    gap: 15px;

    margin-bottom: 18px;
}

#boston-tamagotchi .tama-title {
    font-size: 18px;
    font-weight: bold;
}

#boston-tamagotchi .tama-online {
    display: flex;
    align-items: center;

    gap: 6px;

    font-size: 10px;

    color: var(--muted);
}

#boston-tamagotchi .tama-online-dot {
    width: 7px;
    height: 7px;

    border-radius: 50%;

    background: #777;
}

#boston-tamagotchi.online
.tama-online-dot {
    background: #65b96b;
}

/* ÏÈÒÎÌÅÖ */

#boston-tamagotchi .tama-pet {
    display: flex;

    gap: 22px;

    align-items: center;

    padding: 18px;

    background: var(--panel);

    border-radius: 11px;
}

#boston-tamagotchi .tama-creature {
    display: flex;

    align-items: center;
    justify-content: center;

    flex: 0 0 130px;

    width: 130px;
    height: 130px;

    border-radius: 50%;

    background: #eee;

    font-size: 72px;

    box-shadow:
        inset 0 0 0 5px
        rgba(0,0,0,.08);
}

/* ÑÒÀÒÛ */

#boston-tamagotchi .tama-stats {
    flex: 1;
}

#boston-tamagotchi .tama-stat {
    margin-bottom: 10px;
}

#boston-tamagotchi .tama-stat:last-child {
    margin-bottom: 0;
}

#boston-tamagotchi .tama-stat-top {
    display: flex;

    justify-content: space-between;

    margin-bottom: 4px;

    font-size: 10px;

    color: #bbb;
}

#boston-tamagotchi .tama-bar {
    height: 8px;

    overflow: hidden;

    background: #101217;

    border-radius: 6px;
}

#boston-tamagotchi .tama-bar-fill {
    width: 0;
    height: 100%;

    border-radius: 6px;

    background: #ddd;

    transition:
        width .35s ease;
}

/* ÏÎËÜÇÎÂÀÒÅËÜ */

#boston-tamagotchi .tama-user {
    margin-top: 15px;

    padding: 12px 14px;

    display: flex;

    align-items: center;
    justify-content: space-between;

    gap: 10px;

    background: var(--panel2);

    border: 1px solid var(--border);

    border-radius: 8px;
}

#boston-tamagotchi .tama-user-name {
    color: #bbb;

    font-size: 11px;
}

#boston-tamagotchi .tama-wallet {
    font-size: 11px;

    color: #bbb;
}

#boston-tamagotchi .tama-wallet strong {
    color: #fff;

    font-size: 16px;
}

/* ÊÍÎÏÊÈ */

#boston-tamagotchi .tama-actions {
    display: grid;

    grid-template-columns:
        repeat(2, 1fr);

    gap: 8px;

    margin-top: 15px;
}

#boston-tamagotchi
.tama-action {
    min-height: 43px;

    padding: 8px 12px;

    border: 0;

    border-radius: 7px;

    background: #eceae5;

    color: #181a20;

    font-family: inherit;

    font-size: 11px;
    font-weight: bold;

    cursor: pointer;

    transition:
        transform .1s ease,
        filter .1s ease;
}

#boston-tamagotchi
.tama-action:hover {
    filter: brightness(1.08);
}

#boston-tamagotchi
.tama-action:active {
    transform: scale(.98);
}

#boston-tamagotchi
.tama-action:disabled {
    opacity: .35;

    cursor: default;
}

/* ÐÅÇÓËÜÒÀÒ */

#boston-tamagotchi .tama-message {
    min-height: 38px;

    margin-top: 12px;

    padding: 10px 12px;

    border-radius: 7px;

    background: #111419;

    color: #c5c7cb;

    font-size: 11px;
    line-height: 1.45;
}

/* ÆÓÐÍÀË */

#boston-tamagotchi .tama-log {
    margin-top: 17px;

    padding-top: 15px;

    border-top:
        1px solid var(--border);
}

#boston-tamagotchi .tama-log-title {
    margin-bottom: 8px;

    font-size: 9px;
    font-weight: bold;

    letter-spacing: 1px;

    color: #777;

    text-transform: uppercase;
}

#boston-tamagotchi .tama-log-item {
    padding: 6px 0;

    border-bottom:
        1px solid rgba(255,255,255,.05);

    font-size: 10px;

    color: #aaa;

    line-height: 1.4;
}

#boston-tamagotchi .tama-log-item strong {
    color: #ddd;
}

/* ÌÎÁÈËÜÍÀß ÂÅÐÑÈß */

@media (max-width: 500px) {

    #boston-tamagotchi {
        padding: 15px;
    }

    #boston-tamagotchi .tama-pet {
        flex-direction: column;
    }

    #boston-tamagotchi .tama-creature {
        flex-basis: 110px;

        width: 110px;
        height: 110px;

        font-size: 60px;
    }

    #boston-tamagotchi .tama-stats {
        width: 100%;
    }

}

</style>

<div id="boston-tamagotchi">

    <div class="tama-header">

        <div class="tama-title">
            Íåèçâåñòíîå ñóùåñòâî
        </div>

        <div class="tama-online">

            <span
                class="tama-online-dot"
            ></span>

            <span
                id="tama-connection"
            >
                ïîäêëþ÷åíèå...
            </span>

        </div>

    </div>

    <div class="tama-pet">

        <div class="tama-creature">
            👻
        </div>

        <div class="tama-stats">

            <div class="tama-stat">

                <div class="tama-stat-top">

                    <span>
                        Ñûòîñòü
                    </span>

                    <span
                        id="tama-hunger-text"
                    >
                        —
                    </span>

                </div>

                <div class="tama-bar">

                    <div
                        class="tama-bar-fill"
                        id="tama-hunger"
                    ></div>

                </div>

            </div>

            <div class="tama-stat">

                <div class="tama-stat-top">

                    <span>
                        Íàñòðîåíèå
                    </span>

                    <span
                        id="tama-mood-text"
                    >
                        —
                    </span>

                </div>

                <div class="tama-bar">

                    <div
                        class="tama-bar-fill"
                        id="tama-mood"
                    ></div>

                </div>

            </div>

            <div class="tama-stat">

                <div class="tama-stat-top">

                    <span>
                        Ýíåðãèÿ
                    </span>

                    <span
                        id="tama-energy-text"
                    >
                        —
                    </span>

                </div>

                <div class="tama-bar">

                    <div
                        class="tama-bar-fill"
                        id="tama-energy"
                    ></div>

                </div>

            </div>

            <div class="tama-stat">

                <div class="tama-stat-top">

                    <span>
                        Õàîñ
                    </span>

                    <span
                        id="tama-chaos-text"
                    >
                        —
                    </span>

                </div>

                <div class="tama-bar">

                    <div
                        class="tama-bar-fill"
                        id="tama-chaos"
                    ></div>

                </div>

            </div>

        </div>

    </div>

    <div class="tama-user">

        <div
            class="tama-user-name"
            id="tama-user-name"
        >
            Îïðåäåëÿåì ïîëüçîâàòåëÿ...
        </div>

        <div class="tama-wallet">

            Äåéñòâèé:

            <strong
                id="tama-balance"
            >
                —
            </strong>

            🐾

        </div>

    </div>

    <div class="tama-actions">

        <button
            class="tama-action"
            type="button"
            data-action="feed"
        >
            🍖 Ïîêîðìèòü
        </button>

        <button
            class="tama-action"
            type="button"
            data-action="play"
        >
            🎾 Ïîèãðàòü
        </button>

        <button
            class="tama-action"
            type="button"
            data-action="pet"
        >
            ❤️ Ïîãëàäèòü
        </button>

        <button
            class="tama-action"
            type="button"
            data-action="sleep"
        >
            💤 Óëîæèòü ñïàòü
        </button>

    </div>

    <div
        class="tama-message"
        id="tama-message"
    >
        Êàæäûé èãðîâîé ïîñò äà¸ò îäíî äåéñòâèå.
    </div>

    <div class="tama-log">

        <div class="tama-log-title">
            ×òî ïðîèñõîäèëî
        </div>

        <div
            id="tama-log-list"
        ></div>

    </div>

</div>

<script>

(function () {

    /* ===================================================
       ÍÀÑÒÐÎÉÊÈ
       =================================================== */

    var DATABASE_URL =
        'https://boston-board-game-default-rtdb.europe-west1.firebasedatabase.app';

    /*
    Ñòàðòîâûå õàðàêòåðèñòèêè.
    */

    var BASE_STATS = {

        hunger: 70,

        mood: 65,

        energy: 65,

        chaos: 20

    };

    /*
    Âëèÿíèå äåéñòâèé.
    */

    var ACTIONS = {

        feed: {

            label:
                'ïîêîðìèë ñóùåñòâî',

            result:
                'Ñóùåñòâî äîâîëüíî ñúåëî ïðåäëîæåííîå. Êàæåòñÿ, âìåñòå ñ òàðåëêîé.',

            hunger:
                12,

            mood:
                1,

            energy:
                0,

            chaos:
                1

        },

        play: {

            label:
                'ïîèãðàë ñ ñóùåñòâîì',

            result:
                'Âû ïîèãðàëè. Òåïåðü êòî-òî äîëæåí îáúÿñíèòü, ïî÷åìó ñòóë íàõîäèòñÿ íà ïîòîëêå.',

            hunger:
                -3,

            mood:
                10,

            energy:
                -5,

            chaos:
                5

        },

        pet: {

            label:
                'ïîãëàäèë ñóùåñòâî',

            result:
                'Îíî ïîçâîëèëî ñåáÿ ïîãëàäèòü. Ýòî ïîäîçðèòåëüíî.',

            hunger:
                0,

            mood:
                7,

            energy:
                1,

            chaos:
                -2

        },

        sleep: {

            label:
                'óëîæèë ñóùåñòâî ñïàòü',

            result:
                'Ñóùåñòâî ñâåðíóëîñü êëóáêîì. Èëè ýòî ïðîñòî î÷åíü ñòðàííàÿ ïîçà.',

            hunger:
                -2,

            mood:
                1,

            energy:
                12,

            chaos:
                -2

        }

    };

    /* ===================================================
       ÝËÅÌÅÍÒÛ
       =================================================== */

    var root =
        document.getElementById(
            'boston-tamagotchi'
        );

    if (!root) {
        return;
    }

    var connection =
        document.getElementById(
            'tama-connection'
        );

    var userNameElement =
        document.getElementById(
            'tama-user-name'
        );

    var balanceElement =
        document.getElementById(
            'tama-balance'
        );

    var messageElement =
        document.getElementById(
            'tama-message'
        );

    var logElement =
        document.getElementById(
            'tama-log-list'
        );

    var buttons =
        root.querySelectorAll(
            '.tama-action'
        );

    /* ===================================================
       ÑÎÑÒÎßÍÈÅ
       =================================================== */

    var currentUser = null;

    var databaseData = {};

    var busy = false;

    var stream = null;

    /* ===================================================
       URL
       =================================================== */

    function tamaUrl() {

        return (
            DATABASE_URL +
            '/tamagotchi.json'
        );

    }

    function actionUrl(
        userId,
        postId
    ) {

        return (
            DATABASE_URL +
            '/tamagotchi/actions/' +
            encodeURIComponent(
                userId
            ) +
            '/' +
            encodeURIComponent(
                postId
            ) +
            '.json'
        );

    }

    /* ===================================================
       ÎÁÙÅÍÈÅ Ñ ÐÎÄÈÒÅËÜÑÊÈÌ ÔÎÐÓÌÎÌ
       =================================================== */

    function askForUser() {

        try {

            window.parent.postMessage(
                {
                    type:
                        'BOSTON_TAMA_HELLO'
                },
                '*'
            );

        } catch (error) {}

    }

    window.addEventListener(
        'message',
        function (event) {

            if (
                !event.data ||
                event.data.type !==
                    'BOSTON_TAMA_USER'
            ) {
                return;
            }

            if (
                event.data.loggedIn
            ) {

                currentUser = {

                    id:
                        String(
                            event.data.userId
                        ),

                    name:
                        String(
                            event.data.userName
                        )

                };

                userNameElement.textContent =
                    currentUser.name;

            } else {

                currentUser =
                    null;

                userNameElement.textContent =
                    'Ãîñòü';

                messageElement.textContent =
                    '×òîáû óõàæèâàòü çà ñóùåñòâîì, íåîáõîäèìî âîéòè íà ôîðóì.';

            }

            render();

        }
    );

    askForUser();

    var userTimer =
        setInterval(
            function () {

                if (currentUser) {

                    clearInterval(
                        userTimer
                    );

                    return;

                }

                askForUser();

            },
            1000
        );

    setTimeout(
        function () {

            clearInterval(
                userTimer
            );

        },
        10000
    );

    /* ===================================================
       ÇÀÃÐÓÇÊÀ FIREBASE
       =================================================== */

    async function loadData() {

        try {

            var response =
                await fetch(
                    tamaUrl(),
                    {
                        cache:
                            'no-store'
                    }
                );

            if (!response.ok) {

                throw new Error(
                    'HTTP ' +
                    response.status
                );

            }

            databaseData =
                await response.json() ||
                {};

            root.classList.add(
                'online'
            );

            connection.textContent =
                'îíëàéí';

            render();

        } catch (error) {

            root.classList.remove(
                'online'
            );

            connection.textContent =
                'îøèáêà ñîåäèíåíèÿ';

            console.error(
                'Tamagotchi:',
                error
            );

        }

    }

    /* ===================================================
       ÂÑÅ ÄÅÉÑÒÂÈß
       =================================================== */

    function getAllActions() {

        var result = [];

        var actions =
            databaseData.actions ||
            {};

        Object.keys(
            actions
        ).forEach(
            function (userId) {

                var userActions =
                    actions[userId] ||
                    {};

                Object.keys(
                    userActions
                ).forEach(
                    function (postId) {

                        var action =
                            userActions[
                                postId
                            ];

                        if (!action) {
                            return;
                        }

                        result.push(
                            {

                                userId:
                                    userId,

                                postId:
                                    postId,

                                type:
                                    action.type,

                                userName:
                                    action.userName ||
                                    'Êòî-òî',

                                createdAt:
                                    Number(
                                        action.createdAt
                                    ) || 0

                            }
                        );

                    }
                );

            }
        );

        result.sort(
            function (a, b) {

                return (
                    b.createdAt -
                    a.createdAt
                );

            }
        );

        return result;

    }

    /* ===================================================
       ÍÅÈÑÏÎËÜÇÎÂÀÍÍÛÅ ÏÎÑÒÛ
       =================================================== */

    function getUnusedCredits() {

        if (!currentUser) {
            return [];
        }

        var credits =
            (
                databaseData.credits &&
                databaseData.credits[
                    currentUser.id
                ]
            ) || {};

        var actions =
            (
                databaseData.actions &&
                databaseData.actions[
                    currentUser.id
                ]
            ) || {};

        return Object.keys(
            credits
        )
        .filter(
            function (postId) {

                return !actions[
                    postId
                ];

            }
        )
        .sort(
            function (a, b) {

                return (
                    Number(a) -
                    Number(b)
                );

            }
        );

    }

    /* ===================================================
       ÕÀÐÀÊÒÅÐÈÑÒÈÊÈ ÏÈÒÎÌÖÀ
       =================================================== */

    function clamp(value) {

        return Math.max(
            0,
            Math.min(
                100,
                Math.round(
                    value
                )
            )
        );

    }

    function calculateStats() {

        var stats = {

            hunger:
                BASE_STATS.hunger,

            mood:
                BASE_STATS.mood,

            energy:
                BASE_STATS.energy,

            chaos:
                BASE_STATS.chaos

        };

        var actions =
            getAllActions();

        /*
        Ìåäëåííîå åñòåñòâåííîå
        óõóäøåíèå ïîêàçàòåëåé.

        Îòñ÷¸ò íà÷èíàåòñÿ
        ñ ïåðâîãî äåéñòâèÿ.
        */

        if (actions.length) {

            var oldest =
                actions[
                    actions.length - 1
                ].createdAt;

            var hours =
                Math.max(
                    0,
                    (
                        Date.now() -
                        oldest
                    ) /
                    3600000
                );

            stats.hunger -=
                Math.floor(
                    hours / 6
                ) * 2;

            stats.mood -=
                Math.floor(
                    hours / 12
                );

            stats.energy -=
                Math.floor(
                    hours / 8
                ) * 2;

            stats.chaos -=
                Math.floor(
                    hours / 24
                );

        }

        actions.forEach(
            function (action) {

                var effect =
                    ACTIONS[
                        action.type
                    ];

                if (!effect) {
                    return;
                }

                stats.hunger +=
                    effect.hunger;

                stats.mood +=
                    effect.mood;

                stats.energy +=
                    effect.energy;

                stats.chaos +=
                    effect.chaos;

            }
        );

        stats.hunger =
            clamp(
                stats.hunger
            );

        stats.mood =
            clamp(
                stats.mood
            );

        stats.energy =
            clamp(
                stats.energy
            );

        stats.chaos =
            clamp(
                stats.chaos
            );

        return stats;

    }

    /* ===================================================
       ÏÎËÎÑÊÀ
       =================================================== */

    function setBar(
        name,
        value
    ) {

        var bar =
            document.getElementById(
                'tama-' + name
            );

        var text =
            document.getElementById(
                'tama-' +
                name +
                '-text'
            );

        if (bar) {

            bar.style.width =
                value + '%';

        }

        if (text) {

            text.textContent =
                value + '%';

        }

    }

    /* ===================================================
       ÆÓÐÍÀË
       =================================================== */

    function renderLog() {

        var actions =
            getAllActions()
                .slice(
                    0,
                    8
                );

        logElement.innerHTML = '';

        if (!actions.length) {

            var empty =
                document.createElement(
                    'div'
                );

            empty.className =
                'tama-log-item';

            empty.textContent =
                'Ïîêà íè÷åãî íå ïðîèñõîäèëî. Ñóùåñòâî íàáëþäàåò.';

            logElement.appendChild(
                empty
            );

            return;

        }

        actions.forEach(
            function (action) {

                var effect =
                    ACTIONS[
                        action.type
                    ];

                if (!effect) {
                    return;
                }

                var item =
                    document.createElement(
                        'div'
                    );

                item.className =
                    'tama-log-item';

                var strong =
                    document.createElement(
                        'strong'
                    );

                strong.textContent =
                    action.userName;

                var text =
                    document.createTextNode(
                        ' ' +
                        effect.label +
                        '.'
                    );

                item.appendChild(
                    strong
                );

                item.appendChild(
                    text
                );

                logElement.appendChild(
                    item
                );

            }
        );

    }

    /* ===================================================
       ÎÒÎÁÐÀÆÅÍÈÅ
       =================================================== */

    function render() {

        var stats =
            calculateStats();

        setBar(
            'hunger',
            stats.hunger
        );

        setBar(
            'mood',
            stats.mood
        );

        setBar(
            'energy',
            stats.energy
        );

        setBar(
            'chaos',
            stats.chaos
        );

        var balance =
            getUnusedCredits()
                .length;

        balanceElement.textContent =
            currentUser
                ? balance
                : '—';

        Array.prototype.forEach.call(
            buttons,
            function (button) {

                button.disabled =
                    (
                        busy ||
                        !currentUser ||
                        balance <= 0
                    );

            }
        );

        renderLog();

    }

    /* ===================================================
       ÄÅÉÑÒÂÈÅ
       =================================================== */

    async function performAction(
        type
    ) {

        if (
            busy ||
            !currentUser ||
            !ACTIONS[type]
        ) {
            return;
        }

        busy = true;

        render();

        /*
        Ïåðåä òðàòîé îáíîâëÿåì áàçó,
        ÷òîáû íå èñïîëüçîâàòü
        óæå ïîòðà÷åííûé ïîñò.
        */

        await loadData();

        var unused =
            getUnusedCredits();

        if (!unused.length) {

            messageElement.textContent =
                'Ó âàñ íåò ñâîáîäíûõ äåéñòâèé. Íàïèøèòå èãðîâîé ïîñò — îí äàñò +1 🐾.';

            busy = false;

            render();

            return;

        }

        /*
        Áåð¸ì ñàìûé ñòàðûé
        íåèñïîëüçîâàííûé èãðîâîé ïîñò.
        */

        var postId =
            unused[0];

        try {

            var response =
                await fetch(
                    actionUrl(
                        currentUser.id,
                        postId
                    ),
                    {

                        method:
                            'PUT',

                        headers: {
                            'Content-Type':
                                'application/json'
                        },

                        body:
                            JSON.stringify(
                                {

                                    type:
                                        type,

                                    userName:
                                        currentUser.name,

                                    createdAt:
                                        Date.now()

                                }
                            )

                    }
                );

            if (!response.ok) {

                throw new Error(
                    'HTTP ' +
                    response.status
                );

            }

            messageElement.textContent =
                ACTIONS[type]
                    .result;

            await loadData();

        } catch (error) {

            console.error(
                'Tamagotchi action:',
                error
            );

            messageElement.textContent =
                'Íå óäàëîñü âûïîëíèòü äåéñòâèå. Âîçìîæíî, ýòîò æåòîí óæå áûë ïîòðà÷åí â äðóãîé âêëàäêå.';

            await loadData();

        }

        busy = false;

        render();

    }

    /* ===================================================
       ÊÍÎÏÊÈ
       =================================================== */

    Array.prototype.forEach.call(
        buttons,
        function (button) {

            button.addEventListener(
                'click',
                function () {

                    performAction(
                        this.getAttribute(
                            'data-action'
                        )
                    );

                }
            );

        }
    );

    /* ===================================================
       REALTIME
       =================================================== */

    function startRealtime() {

        try {

            stream =
                new EventSource(
                    tamaUrl()
                );

            stream.addEventListener(
                'open',
                function () {

                    root.classList.add(
                        'online'
                    );

                    connection.textContent =
                        'îíëàéí';

                }
            );

            stream.addEventListener(
                'put',
                function () {

                    loadData();

                }
            );

            stream.addEventListener(
                'patch',
                function () {

                    loadData();

                }
            );

            stream.onerror =
                function () {

                    connection.textContent =
                        'ïåðåïîäêëþ÷åíèå...';

                };

        } catch (error) {

            console.error(
                error
            );

        }

    }

    /*
    Äàæå åñëè EventSource âíóòðè
    Safe HTML îêàæåòñÿ çàáëîêèðîâàí,
    ðàç â 10 ñåêóíä âñ¸ ðàâíî
    îáíîâëÿåì ñîñòîÿíèå.
    */

    setInterval(
        loadData,
        10000
    );

    /* ===================================================
       ÇÀÏÓÑÊ
       =================================================== */

    loadData();

    startRealtime();

})();

</script>

[/html]

Ïîäïèñü àâòîðà

https://i.imgur.com/49teI7T.gif

https://i.imgur.com/jUx1TrW.gif

https://i.imgur.com/ya9ZQ1b.gif

https://i.imgur.com/7WCOt97.gif

https://i.imgur.com/k3Qir0g.gif