Layout

 avatar
unknown
html
a year ago
34 kB
11
Indexable
{% extends 'base.html' %}
{% load static %}

{% block content %}

    <div id="layout" v-cloak>
        <div class="flex items-center justify-center h-screen" v-if="isPageLoading">

            <div class="loading-spinner">
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
        </div>

        <div v-else>
            <!-- Off-canvas menu for mobile, show/hide based on off-canvas menu state. -->
            <div v-if="isSideNavOpen" class="relative z-50 lg:hidden" role="dialog" aria-modal="true">
                <!--
                  Off-canvas menu backdrop, show/hide based on off-canvas menu state.

                  Entering: "transition-opacity ease-linear duration-300"
                    From: "opacity-0"
                    To: "opacity-100"
                  Leaving: "transition-opacity ease-linear duration-300"
                    From: "opacity-100"
                    To: "opacity-0"
                -->
                <div class="fixed inset-0 bg-gray-900/80"></div>

                <div class="fixed inset-0 flex">
                    <!--
                      Off-canvas menu, show/hide based on off-canvas menu state.

                      Entering: "transition ease-in-out duration-300 transform"
                        From: "-translate-x-full"
                        To: "translate-x-0"
                      Leaving: "transition ease-in-out duration-300 transform"
                        From: "translate-x-0"
                        To: "-translate-x-full"
                    -->
                    <div class="relative mr-16 flex w-full max-w-xs flex-1">
                        <!--
                          Close button, show/hide based on off-canvas menu state.

                          Entering: "ease-in-out duration-300"
                            From: "opacity-0"
                            To: "opacity-100"
                          Leaving: "ease-in-out duration-300"
                            From: "opacity-100"
                            To: "opacity-0"
                        -->
                        <div class="absolute left-full top-0 flex w-16 justify-center pt-5">
                            <button @click="openSideNavOpen" type="button" class="-m-2.5 p-2.5">
                                <span class="sr-only">Close sidebar</span>
                                <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                                     stroke="currentColor" aria-hidden="true">
                                    <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
                                </svg>
                            </button>
                        </div>

                        <!-- Sidebar component, swap this element with another sidebar if you like -->
                        <div class="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-4">
                            <div class="flex h-16 shrink-0 items-center mt-5">
                                <img class="mx-auto h-10 w-auto" src="{% static 'logo.png' %}">
                            </div>
                            <nav class="flex flex-1 flex-col">
                                <ul role="list" class="flex flex-1 flex-col gap-y-7">
                                    <li>
                                        <ul role="list" class="-mx-2 space-y-1">
                                            <li>
                                                <!-- Current: "bg-gray-50 text-indigo-600", Default: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50" -->
                                                <a href="/"
                                                   class="bg-gray-50 text-indigo-600 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
                                                    <svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none"
                                                         viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
                                                         aria-hidden="true">
                                                        <path stroke-linecap="round" stroke-linejoin="round"
                                                              d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/>
                                                    </svg>
                                                    Homepage
                                                </a>
                                            </li>
                                            <li>
                                                <a href="/chats/room"
                                                   class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
                                                    <svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
                                                         fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                                                         stroke="currentColor" aria-hidden="true">
                                                        <path stroke-linecap="round" stroke-linejoin="round"
                                                              d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
                                                    </svg>
                                                    Chat Room
                                                </a>
                                            </li>
                                            <li>
                                                <a href="/tasks"
                                                   class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
                                                    <svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
                                                         fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                                                         stroke="currentColor" aria-hidden="true">
                                                        <path stroke-linecap="round" stroke-linejoin="round"
                                                              d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"/>
                                                    </svg>
                                                    Tasks
                                                </a>
                                            </li>
                                        </ul>
                                    </li>
                                </ul>
                            </nav>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Static sidebar for desktop -->
            <div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
                <!-- Sidebar component, swap this element with another sidebar if you like -->
                <div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 pb-4">
                    <div class="flex h-16 shrink-0 items-center mt-20">
                        <img class="mx-auto h-24 w-auto" src="{% static 'logo.png' %}">
                    </div>
                    <nav class="mt-10 flex flex-1 flex-col">
                        <ul role="list" class="flex flex-1 flex-col gap-y-7">
                            <li>
                                <ul role="list" class="-mx-2 space-y-1">
                                    <li>
                                        <!-- Current: "bg-gray-50 text-indigo-600", Default: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50" -->
                                        <a href="/"
                                           class="bg-gray-50 text-indigo-600 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
                                            <svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none"
                                                 viewBox="0 0 24 24"
                                                 stroke-width="1.5" stroke="currentColor" aria-hidden="true">
                                                <path stroke-linecap="round" stroke-linejoin="round"
                                                      d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/>
                                            </svg>
                                            Homepage
                                        </a>
                                    </li>
                                    <li>
                                        <a href="/chats/room"
                                           class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
                                            <svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
                                                 fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                                                 stroke="currentColor"
                                                 aria-hidden="true">
                                                <path stroke-linecap="round" stroke-linejoin="round"
                                                      d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
                                            </svg>
                                            Chat Room
                                        </a>
                                    </li>
                                    <li>
                                        <a href="/tasks"
                                           class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
                                            <svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
                                                 fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                                                 stroke="currentColor"
                                                 aria-hidden="true">
                                                <path stroke-linecap="round" stroke-linejoin="round"
                                                      d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"/>
                                            </svg>
                                            Tasks
                                        </a>
                                    </li>
                                </ul>
                            </li>
                        </ul>
                    </nav>
                </div>
            </div>

            <div class="lg:pl-72">
                <div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
                    <button @click="openSideNavOpen" type="button" class="-m-2.5 p-2.5 text-gray-700 lg:hidden">
                        <span class="sr-only">Open sidebar</span>
                        <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
                             aria-hidden="true">
                            <path stroke-linecap="round" stroke-linejoin="round"
                                  d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
                        </svg>
                    </button>

                    <!-- Separator -->
                    <div class="h-6 w-px bg-gray-200 lg:hidden" aria-hidden="true"></div>

                    <div class="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
                        <div class="relative flex flex-1" >

                        </div>
                        <div class="flex items-center gap-x-4 lg:gap-x-6">


                            <!-- Separator -->
                            <div class="hidden lg:block lg:h-6 lg:w-px lg:bg-gray-200" aria-hidden="true"></div>

                            <!-- Profile dropdown -->
                            <div class="relative">
                                <button @click="openSidebar" type="button" class="-m-1.5 flex items-center p-1.5"
                                        id="user-menu-button"
                                        aria-expanded="false" aria-haspopup="true">
                                    <span class="sr-only">Open user menu</span>
                                    <img class="h-8 w-8 rounded-full bg-gray-50"
                                         src="{{ user.profile_picture.url }}"
                                         alt="">
                                    <span class="hidden lg:flex lg:items-center">
                                        <span class="ml-4 text-sm font-semibold leading-6 text-white"
                                              aria-hidden="true">{{ user.first_name }} {{ user.last_name }}</span>
                                        <svg class="ml-2 h-5 w-5 text-white" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                                          <path fill-rule="evenodd"
                                                d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
                                                clip-rule="evenodd"/>
                                        </svg>
                                    </span>
                                </button>

                                <div v-if="isOpen"
                                     class="absolute right-0 z-10 mt-2.5 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none"
                                     role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button"
                                     tabindex="-1">
                                    <!-- Active: "bg-gray-50", Not Active: "" -->
                                    <a href="/profile" class="block px-3 py-1 text-sm leading-6 text-gray-900"
                                       role="menuitem"
                                       tabindex="-1" id="user-menu-item-1">Profile</a>
                                    <a href="/accounts/logout/" class="block px-3 py-1 text-sm leading-6 text-gray-900"
                                       role="menuitem"
                                       tabindex="-1" id="user-menu-item-1">Sign out</a>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <main class="py-10">
                    <div class="mx-auto max-w-9xl px-4 sm:px-10 lg:px-8">
                        {% block layout %}
                        {% endblock %}
                    </div>
                </main>
            </div>
        </div>
    </div>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script type="module">

        const {createApp} = Vue;
        const delimiters = ['[[', ']]']
        const chat_room = "{{ pk }}"
        const user_id = "{{ user.id }}"
        createApp({
            delimiters,
            data() {
                return {
                    isOpen: false,
                    isSideNavOpen: false,

                    todoList: [],
                    inProgressList: [],
                    doneList: [],

                    showTasks: true,
                    showAIModal: false,
                    isLoading: false,
                    isChatFormModalOpen: false,
                    isPageLoading: false,
                    isDeleteModalOpen: false,

                    prompt: "",
                    title: "",
                    assignee: "",
                    deadline: "",
                    description: "",
                    chat_name: "",
                    message: "",
                    chat_room_id: "",
                    chat_members: [],
                    chat_rooms: [],
                    chat_messages: [],
                    currentDate: new Date(),
                    user_id
                }
            },
            methods: {
                openSidebar() {
                    this.isOpen = !this.isOpen
                },
                openSideNavOpen() {
                    this.isSideNavOpen = !this.isSideNavOpen
                },
                openTasksForm() {
                    this.showTasks = !this.showTasks
                },
                openAIModal() {
                    this.showAIModal = !this.showAIModal
                },
                openChatFormModalOpen() {
                    this.isChatFormModalOpen = !this.isChatFormModalOpen
                },
                deleteChatRoom(id) {
                    this.isDeleteModalOpen = !this.isDeleteModalOpen
                    this.chat_room_id = id
                },

                async getTasksToDo() {
                    try {
                        const resp = await apiGetTasksToDo()
                        this.todoList = resp
                    } catch (err) {
                        console.log(err)
                    }
                },

                async getTasksInProgress() {
                    try {
                        const resp = await apiGetTasksInProgress()
                        this.inProgressList = resp
                    } catch (err) {
                        console.log(err)
                    }
                },

                async getTasksDone() {
                    try {
                        const resp = await apiGetTasksDone()
                        this.doneList = resp
                    } catch (err) {
                        console.log(err)
                    }
                },

                async moveTasks(id, status) {
                    const payload = {
                        id: id,
                        status: status
                    }
                    try {
                        const resp = await apiMoveTasks(payload)
                        await this.getTasksToDo()
                        await this.getTasksInProgress()
                        await this.getTasksDone()
                    } catch (err) {
                        console.log(err)
                    }

                },

                async aIGenerate() {
                    const payload = {
                        prompt: this.prompt
                    }
                    try {
                        this.isLoading = !this.isLoading
                        const resp = await apiAIGenerate(payload)
                        this.isLoading = !this.isLoading
                        this.description = resp

                        if (resp) {
                            this.isLoading = !this.isLoading
                            this.showAIModal = !this.showAIModal
                        }

                    } catch (err) {
                        console.log(err)
                    }

                },

                async createTasks() {

                    const payload = {
                        title: this.title,
                        assignee: this.assignee,
                        deadline: this.deadline,
                        description: this.description
                    }
                    try {

                        const resp = await apiAddTasks(payload)

                        if (resp) {
                            this.showTasks = !this.showTasks
                        }

                    } catch (err) {
                        console.log(err)
                    }
                },

                async createNewChatRoom() {

                    const payload = {
                        name: this.chat_name,
                        members: this.chat_members
                    }
                    try {

                        const resp = await apiCreateNewChatRoom(payload)

                        if (resp) {
                            this.isChatFormModalOpen = !this.isChatFormModalOpen
                            await this.getChatRoomsLists()
                        }

                    } catch (err) {
                        console.log(err)
                    }
                },

                async getChatRoomsLists() {
                    try {
                        const resp = await apiChatRoomList()
                        this.chat_rooms = resp
                    } catch (err) {
                        console.log(err)
                    }
                },

                formatRelativeTime(dateString) {
                    const now = new Date();
                    const date = new Date(dateString);

                    const diffInMilliseconds = now - date;
                    const diffInSeconds = Math.floor(diffInMilliseconds / 1000);

                    if (diffInSeconds < 1) {
                        return 'just now';
                    } else if (diffInSeconds < 60) {
                        return `last ${diffInSeconds} seconds ago`;
                    } else {
                        const diffInMinutes = Math.floor(diffInSeconds / 60);
                        const diffInHours = Math.floor(diffInMinutes / 60);

                        if (diffInMinutes < 1) {
                            return 'just now';
                        } else if (diffInHours < 1) {
                            return `last ${diffInMinutes} minutes ago`;
                        } else if (diffInHours < 24) {
                            return `last ${diffInHours} hours ago`;
                        } else {
                            const options = {
                                year: 'numeric',
                                month: 'short',
                                day: 'numeric',
                                hour: 'numeric',
                                minute: 'numeric',
                                second: 'numeric',
                            };
                            return new Intl.DateTimeFormat('en-US', options).format(date);
                        }
                    }
                },

                async getChatMessages() {

                    window.setInterval(async () => {
                        try {
                            const resp = await apiGetChatMessages()
                            this.chat_messages = resp
                        } catch (err) {
                            console.log(err)
                        }
                    }, 1000)

                },

                async sendChatMessage() {

                    const payload = {
                        chat_room: chat_room,
                        message: this.message
                    }
                    try {

                        const resp = await apiSendChatMessage(payload)

                        if (resp) {
                            await this.getChatMessages()
                            this.message = ""
                        }

                    } catch (err) {
                        console.log(err)
                    }
                },

            },
            async mounted() {
                await this.getTasksToDo()
                await this.getTasksInProgress()
                await this.getTasksDone()
                await this.getChatRoomsLists()

                if (chat_room) {
                    await this.getChatMessages()
                }

                this.isPageLoading = false;

            },

            created() {
                this.isPageLoading = true
            },

            computed: {
                formattedDate() {
                    const options = {
                        weekday: 'long',
                        month: 'long',
                        day: 'numeric',
                        year: 'numeric',
                    };
                    return this.currentDate.toLocaleDateString('en-US', options);
                },
            },

        }).mount('#layout')


        async function apiGetTasksToDo() {
            try {
                const resp = await fetch(`/api/tasks/todo`, {
                    method: 'GET'
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }

        async function apiGetTasksInProgress() {
            try {
                const resp = await fetch(`/api/tasks/in-progress`, {
                    method: 'GET'
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }

        async function apiGetTasksDone() {
            try {
                const resp = await fetch(`/api/tasks/done`, {
                    method: 'GET'
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }

        async function apiMoveTasks(payload) {
            try {
                const resp = await fetch(`/api/tasks/move`, {
                    method: 'POST',
                    body: JSON.stringify(payload),
                    headers: {"Content-Type": "application/json"},
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }

        async function apiAIGenerate(payload) {
            try {
                const resp = await fetch(`/api/ai/generate`, {
                    method: 'POST',
                    body: JSON.stringify(payload),
                    headers: {"Content-Type": "application/json"},
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }

        async function apiAddTasks(payload) {
            try {
                const resp = await fetch(`/api/tasks/add`, {
                    method: 'POST',
                    body: JSON.stringify(payload),
                    headers: {"Content-Type": "application/json"},
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }


        async function apiCreateNewChatRoom(payload) {
            try {
                const resp = await fetch(`/api/chat/room/add`, {
                    method: 'POST',
                    body: JSON.stringify(payload),
                    headers: {"Content-Type": "application/json"},
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }

        async function apiChatRoomList() {
            try {
                const resp = await fetch(`/api/chat/rooms`, {
                    method: 'GET'
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }


        async function apiGetChatMessages() {
            try {
                const resp = await fetch(`/api/chat/messages/${chat_room}`, {
                    method: 'GET'
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }

        async function apiSendChatMessage(payload) {
            try {
                const resp = await fetch(`/api/chat/message/send`, {
                    method: 'POST',
                    body: JSON.stringify(payload),
                    headers: {"Content-Type": "application/json"},
                }).then(resp => resp.json())
                return resp
            } catch (error) {
                console.log(error)
                return []
            }
        }
    </script>

    <style>
        [v-cloak] {
            display: none;
        }

        .spinner {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            border: 9px solid #474bff;
            animation: spinner-bulqg1 0.8s infinite linear alternate,
            spinner-oaa3wk 1.6s infinite linear;
        }

        @keyframes spinner-bulqg1 {
            0% {
                clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%);
            }

            12.5% {
                clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0%);
            }

            25% {
                clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100%);
            }

            50% {
                clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%);
            }

            62.5% {
                clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%);
            }

            75% {
                clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100%);
            }

            100% {
                clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100%);
            }
        }

        @keyframes spinner-oaa3wk {
            0% {
                transform: scaleY(1) rotate(0deg);
            }

            49.99% {
                transform: scaleY(1) rotate(135deg);
            }

            50% {
                transform: scaleY(-1) rotate(0deg);
            }

            100% {
                transform: scaleY(-1) rotate(-135deg);
            }
        }


        .loading-spinner {
            width: 44.8px;
            height: 44.8px;
            animation: loading-spinner-y0fdc1 2s infinite ease;
            transform-style: preserve-3d;
        }

        .loading-spinner > div {
            background-color: rgba(71, 75, 255, 0.2);
            height: 100%;
            position: absolute;
            width: 100%;
            border: 2.2px solid #474bff;
        }

        .loading-spinner div:nth-of-type(1) {
            transform: translateZ(-22.4px) rotateY(180deg);
        }

        .loading-spinner div:nth-of-type(2) {
            transform: rotateY(-270deg) translateX(50%);
            transform-origin: top right;
        }

        .loading-spinner div:nth-of-type(3) {
            transform: rotateY(270deg) translateX(-50%);
            transform-origin: center left;
        }

        .loading-spinner div:nth-of-type(4) {
            transform: rotateX(90deg) translateY(-50%);
            transform-origin: top center;
        }

        .loading-spinner div:nth-of-type(5) {
            transform: rotateX(-90deg) translateY(50%);
            transform-origin: bottom center;
        }

        .loading-spinner div:nth-of-type(6) {
            transform: translateZ(22.4px);
        }

        @keyframes loading-spinner-y0fdc1 {
            0% {
                transform: rotate(45deg) rotateX(-25deg) rotateY(25deg);
            }

            50% {
                transform: rotate(45deg) rotateX(-385deg) rotateY(25deg);
            }

            100% {
                transform: rotate(45deg) rotateX(-385deg) rotateY(385deg);
            }
        }
    </style>
{% endblock %}
Leave a Comment