frontend cleanup

This commit is contained in:
Priler
2026-01-04 22:50:34 +05:00
parent 091a41ac33
commit 0c28304840
38 changed files with 2873 additions and 1180 deletions

19
frontend/_src/App.svelte Normal file
View File

@@ -0,0 +1,19 @@
<!-- src/App.svelte -->
<script>
import { Router } from "@roxi/routify";
import routes from "../.routify/routes.default.js";
import { SvelteUIProvider } from '@svelteuidev/core';
import Events from "./Events.svelte";
/** START LISTENING **/
// import { startListening } from "./functions";
// startListening();
</script>
<SvelteUIProvider themeObserver='dark' withNormalizeCSS withGlobalStyles>
<Router {routes} />
</SvelteUIProvider>
<Events />

View File

@@ -0,0 +1,44 @@
<script>
import { onMount, onDestroy } from 'svelte'
import { emit, listen } from '@tauri-apps/api/event'
import { resolveResource } from '@tauri-apps/api/path'
import {Howl, Howler} from 'howler';
let assistant_voice_val = "jarvis-og";
import { assistant_voice } from "@/stores"
import { invoke } from "@tauri-apps/api/core";
assistant_voice.subscribe(value => {
assistant_voice_val = value;
});
onMount(async () => {
await listen('audio-play', async (event) => {
// event.event is the event name (useful if you want to use a single callback fn for multiple event types)
// event.payload is the payload object
// let path = await resolveResource('sound/' + (assistant_voice_val == "" ? "jarvis-remake":assistant_voice_val) + '/' + event.payload['data'] + '.wav');
// console.log(path);
// let sound = new Howl({
// src: [path],
// html5: true
// });
// sound.play();
let filename = 'sound/' + (assistant_voice_val == "" ? "jarvis-remake":assistant_voice_val) + '/' + event.payload['data'] + '.wav';
await invoke("play_sound", {
filename: filename,
sleep: true
});
});
await listen('assistant-greet', (event) => {
document.getElementById("arc-reactor")?.classList.add("active");
});
await listen('assistant-waiting', (event) => {
document.getElementById("arc-reactor")?.classList.remove("active");
});
});
</script>

View File

@@ -0,0 +1,73 @@
<script>
import { invoke } from "@tauri-apps/api/core"
import { tg_official_link, github_repository_link } from "@/stores";
let current_year = new Date().getFullYear();
let author_name = "";
(async () => {
author_name = await invoke("get_author_name")
})().catch(err => {
console.error(err);
});
</script>
<footer id="footer">
<p>© {current_year}. Автор проекта: {author_name}</p>
<p style="margin-top: 5px;margin-bottom: 15px;">
<a href="{tg_official_link}" target="_blank" class="special-link"><img src="/media/icons/howdy-logo.png" alt="" width="20px">&nbsp;&nbsp;Наш телеграм</a> канал.
&nbsp;&nbsp;
<a href="{github_repository_link}" target="_blank"><img src="/media/icons/github-logo.png" alt="" width="18px">&nbsp;Github репозиторий</a> проекта.</p>
</footer>
<style lang="scss">
#footer {
text-align: center;
color: #565759;
font-size: 12px;
font-weight: bold;
line-height: 1.7em;
margin-top: 15px;
p {
margin: 0;
padding: 0;
}
a {
color: #185876;
text-decoration: none;
transition: opacity .5s;
img {
opacity: .5;
transition: opacity .5s;
margin-top: -4px;
}
&:hover {
color: #2A9CD0;
img {
opacity: 1;
}
}
&.special-link {
color: #941d92;
display: inline-block;
&:hover {
color: #FF07FC;
background: url(/media/images/bg/bg24.gif);
background-repeat: no-repeat;
background-size: contain;
}
}
}
}
</style>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { invoke } from "@tauri-apps/api/core"
import { Dashboard, Gear } from 'radix-icons-svelte'
import {isActive} from '@roxi/routify'
let app_version = "";
(async () => {
app_version = await invoke("get_app_version")
})().catch(err => {
console.error(err);
});
</script>
<header id="header">
<div class="logo">
<a href="/" title="Проект канала Хауди Хо!"><img src="/media/header-logo.png" alt=""></a>
<div>
<h1><a href="/">JARVIS</a></h1>
<h2>v{app_version} <small style="color: #8AC832;opacity: .9;font-size: 13px;">BETA</small></h2>
</div>
</div>
<nav class="top-menu">
<ul>
<li><a href="/commands" class:active={$isActive('/commands')}><Dashboard /> Команды</a></li>
<li><a href="/settings" class:active={$isActive('/settings')}><Gear /> Настройки</a></li>
</ul>
</nav>
</header>

View File

@@ -0,0 +1,5 @@
<nav>
<a href="/index">Main Page</a>
<a href="/settings">Настройки</a>
</nav>

View File

@@ -0,0 +1,647 @@
<!-- Based on: https://github.com/rembertdesigns/Iron-Man-Arc-Reactor-Pure-CSS and https://codepen.io/FlyingEmu/pen/DZNqEj -->
<div id="arc-reactor" class="reactor-container">
<div class="reactor-container-inner circle abs-center">
<ul class="marks"><li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li></ul>
<div class="e7">
<div class="semi_arc_3 e5_1">
<div class="semi_arc_3 e5_2">
<div class="semi_arc_3 e5_3">
<div class="semi_arc_3 e5_4" />
</div>
</div>
</div>
</div>
</div>
<div class="tunnel circle abs-center" />
<div class="core-wrapper circle abs-center" />
<div class="core-outer circle abs-center" />
<div class="core-inner circle abs-center" />
<div class="coil-container">
<div class="coil coil-1" />
<div class="coil coil-2" />
<div class="coil coil-3" />
<div class="coil coil-4" />
<div class="coil coil-5" />
<div class="coil coil-6" />
<div class="coil coil-7" />
<div class="coil coil-8" />
</div>
</div>
<style lang="scss" global>
$arc_radius: 130px;
$size3: 6px;
$cshadow: rgba(2, 254, 255, 0.8);
$marks_color_1: rgba(2, 254, 255, 1);
$marks_color_2: rgba(2, 254, 255, 0.3);
$colour1: rgba(2, 255, 255, 0.15);
$colour3: rgba(2, 255, 255, 0.3);
$cshadow: rgba(2, 254, 255, 0.8);
.reactor-container {
width: 300px;
height: 320px;
margin: auto;
// border: 1px dashed #888;
position: relative;
border-radius: 50%;
transition: scale 1s ease, opacity .5s ease;
scale: 0.9;
opacity: .9;
// background-color: #384c50;
// border: 1px solid #121414;
// box-shadow: 0px 0px 32px 8px #121414, 0px 0px 4px 1px #121414 inset;
ul {
list-style: none;
margin: 0;
padding: 0;
}
}
.reactor-container-inner {
height: 238px;
width: 238px;
background-color: #161a1b;
box-shadow: 0px 0px 50px 15px $colour3, inset 0px 0px 50px 15px $colour3;
// box-shadow: 0px 0px 4px 1px #52fefe;
}
.circle {
border-radius: 50%;
}
.abs-center {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
.core-inner {
width: 70px;
height: 70px;
border: 5px solid #1b4e5f;
background-color: #ffffff;
box-shadow: 0px 0px 7px 5px #52fefe, 0px 0px 10px 10px #52fefe inset;
}
.core-outer {
width: 120px;
height: 120px;
border: 1px solid #52fefe;
background-color: #ffffff;
box-shadow: 0px 0px 2px 1px #52fefe, 0px 0px 10px 5px #52fefe inset;
}
.core-wrapper {
width: 180px;
height: 180px;
background-color: #073c4b;
box-shadow: 0px 0px 5px 4px #52fefe, 0px 0px 6px 2px #52fefe inset;
}
.tunnel {
width: 220px;
height: 220px;
background-color: #ffffff;
box-shadow: 0px 0px 5px 1px #52fefe, 0px 0px 5px 4px #52fefe inset;
}
.coil-container {
position: relative;
width: 100%;
height: 100%;
animation: 10s infinite linear reactor-anim;
transition: animation 1s;
}
.coil {
position: absolute;
width: 30px;
height: 20px;
top: calc(50% - 110px);
left: calc(50% - 15px);
transform-origin: 15px 110px;
background-color: #073c4b;
box-shadow: 0px 0px 5px #52fefe inset;
}
.coil-1 {
transform: rotate(0deg);
}
.coil-2 {
transform: rotate(45deg);
}
.coil-3 {
transform: rotate(90deg);
}
.coil-4 {
transform: rotate(135deg);
}
.coil-5 {
transform: rotate(180deg);
}
.coil-6 {
transform: rotate(225deg);
}
.coil-7 {
transform: rotate(270deg);
}
.coil-8 {
transform: rotate(315deg);
}
@keyframes reactor-anim {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@mixin border-radius($pixel...) {
border-radius: $pixel;
}
.e7 {
position: relative;
z-index: 1;
width: 160%;
height: 160%;
left: -32.5%;
top: -32.5%;
right: 0;
bottom: 0;
margin: auto;
border: $size3 solid transparent;
background: transparent;
@include border-radius(50%);
transform: rotateZ(0deg);
transition: box-shadow 3s ease;
text-align: center;
opacity: .3;
}
.semi_arc {
width: 100px;
height: 100px;
border: 6px solid #02feff;
background: rgba(2, 254, 255, 0.2);
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
transform: rotateZ(0deg);
transition: box-shadow 3s ease;
text-align: center;
overflow: hidden;
}
.semi_arc:hover {
box-shadow: 0px 0px 30px $cshadow;
transition: 0.3s;
}
.semi_arc_2 {
content: "";
position: absolute;
width: 94%;
height: 94%;
left: 3%;
top: 3%;
border: 5px solid #02feff;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
animation: rotate 4s linear infinite;
text-align: center;
overflow: hidden;
}
.semi_arc_2:after {
content: "";
position: absolute;
width: 94%;
height: 94%;
left: 3%;
top: 3%;
border: 4px solid #02feff;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
animation: rotate_anti 2s linear infinite;
}
.semi_arc_3 {
content: "";
position: absolute;
width: 94%;
height: 94%;
left: 3%;
top: 3%;
border: 5px solid #02feff;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
animation: rotate 4s linear infinite;
text-align: center;
overflow: hidden;
}
.e1:after {
border-color: rgba(2, 255, 255, 0.6);
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
.e2:after {
border-color: rgba(2, 255, 255, 0.6);
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid transparent;
}
.e3 {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
animation: rotate 5s linear infinite;
}
.e3:after {
border-color: rgba(2, 255, 255, 0.6);
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
}
.e4 {
width: 150px;
height: 150px;
}
.e4_1 {
border-color: rgba(2, 255, 255, 0.3);
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
.e4_1:after {
border-color: rgba(2, 255, 255, 0.6);
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
}
.e5 {
width: 200px;
height: 200px;
}
.e5_1 {
color: rgba(2, 255, 255, 0.15);
border: 2px solid;
border-left: 2px solid transparent;
animation: rotate 5s linear infinite;
}
.e5_2 {
color: rgba(2, 255, 255, 0.7);
border: 4px solid;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
animation: rotate_anti 4s linear infinite;
}
.e5_3 {
color: rgba(2, 255, 255, 0.5);
border: 2px solid;
border-left: 2px solid transparent;
border-right: 2px solid transparent;
animation: rotate 3s linear infinite;
}
.e5_4 {
color: rgba(2, 255, 255, 0.15);
border: 4px solid;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid transparent;
animation: rotate_anti 2s linear infinite;
}
.e6 {
border-color: transparent;
background: rgba(255, 255, 255, 0);
width: 200px;
height: 200px;
}
@keyframes rotate_anti {
0% {
transform: rotateZ(360deg);
}
100% {
transform: rotateZ(0deg);
}
}
@keyframes rotate {
0% {
transform: rotateZ(0deg);
}
100% {
transform: rotateZ(360deg);
}
}
.marks {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
li {
display: block;
width: 3px;
height: 11px;
background: $cshadow;
position: absolute;
margin-left: 117.5px;
margin-top: 113.5px;
animation: colour_ease2 3s infinite ease-in-out;
}
}
@keyframes colour_ease2 {
0% {
background: $marks_color_1;
}
50% {
background: $marks_color_2;
}
100% {
background: $marks_color_1;
}
}
.marks li:first-child {
transform: rotate(6deg) translateY($arc_radius);
}
.marks li:nth-child(2) {
transform: rotate(12deg) translateY($arc_radius);
}
.marks li:nth-child(3) {
transform: rotate(18deg) translateY($arc_radius);
}
.marks li:nth-child(4) {
transform: rotate(24deg) translateY($arc_radius);
}
.marks li:nth-child(5) {
transform: rotate(30deg) translateY($arc_radius);
}
.marks li:nth-child(6) {
transform: rotate(36deg) translateY($arc_radius);
}
.marks li:nth-child(7) {
transform: rotate(42deg) translateY($arc_radius);
}
.marks li:nth-child(8) {
transform: rotate(48deg) translateY($arc_radius);
}
.marks li:nth-child(9) {
transform: rotate(54deg) translateY($arc_radius);
}
.marks li:nth-child(10) {
transform: rotate(60deg) translateY($arc_radius);
}
.marks li:nth-child(11) {
transform: rotate(66deg) translateY($arc_radius);
}
.marks li:nth-child(12) {
transform: rotate(72deg) translateY($arc_radius);
}
.marks li:nth-child(13) {
transform: rotate(78deg) translateY($arc_radius);
}
.marks li:nth-child(14) {
transform: rotate(84deg) translateY($arc_radius);
}
.marks li:nth-child(15) {
transform: rotate(90deg) translateY($arc_radius);
}
.marks li:nth-child(16) {
transform: rotate(96deg) translateY($arc_radius);
}
.marks li:nth-child(17) {
transform: rotate(102deg) translateY($arc_radius);
}
.marks li:nth-child(18) {
transform: rotate(108deg) translateY($arc_radius);
}
.marks li:nth-child(19) {
transform: rotate(114deg) translateY($arc_radius);
}
.marks li:nth-child(20) {
transform: rotate(120deg) translateY($arc_radius);
}
.marks li:nth-child(21) {
transform: rotate(126deg) translateY($arc_radius);
}
.marks li:nth-child(22) {
transform: rotate(132deg) translateY($arc_radius);
}
.marks li:nth-child(23) {
transform: rotate(138deg) translateY($arc_radius);
}
.marks li:nth-child(24) {
transform: rotate(144deg) translateY($arc_radius);
}
.marks li:nth-child(25) {
transform: rotate(150deg) translateY($arc_radius);
}
.marks li:nth-child(26) {
transform: rotate(156deg) translateY($arc_radius);
}
.marks li:nth-child(27) {
transform: rotate(162deg) translateY($arc_radius);
}
.marks li:nth-child(28) {
transform: rotate(168deg) translateY($arc_radius);
}
.marks li:nth-child(29) {
transform: rotate(174deg) translateY($arc_radius);
}
.marks li:nth-child(30) {
transform: rotate(180deg) translateY($arc_radius);
}
.marks li:nth-child(31) {
transform: rotate(186deg) translateY($arc_radius);
}
.marks li:nth-child(32) {
transform: rotate(192deg) translateY($arc_radius);
}
.marks li:nth-child(33) {
transform: rotate(198deg) translateY($arc_radius);
}
.marks li:nth-child(34) {
transform: rotate(204deg) translateY($arc_radius);
}
.marks li:nth-child(35) {
transform: rotate(210deg) translateY($arc_radius);
}
.marks li:nth-child(36) {
transform: rotate(216deg) translateY($arc_radius);
}
.marks li:nth-child(37) {
transform: rotate(222deg) translateY($arc_radius);
}
.marks li:nth-child(38) {
transform: rotate(228deg) translateY($arc_radius);
}
.marks li:nth-child(39) {
transform: rotate(234deg) translateY($arc_radius);
}
.marks li:nth-child(40) {
transform: rotate(240deg) translateY($arc_radius);
}
.marks li:nth-child(41) {
transform: rotate(246deg) translateY($arc_radius);
}
.marks li:nth-child(42) {
transform: rotate(252deg) translateY($arc_radius);
}
.marks li:nth-child(43) {
transform: rotate(258deg) translateY($arc_radius);
}
.marks li:nth-child(44) {
transform: rotate(264deg) translateY($arc_radius);
}
.marks li:nth-child(45) {
transform: rotate(270deg) translateY($arc_radius);
}
.marks li:nth-child(46) {
transform: rotate(276deg) translateY($arc_radius);
}
.marks li:nth-child(47) {
transform: rotate(282deg) translateY($arc_radius);
}
.marks li:nth-child(48) {
transform: rotate(288deg) translateY($arc_radius);
}
.marks li:nth-child(49) {
transform: rotate(294deg) translateY($arc_radius);
}
.marks li:nth-child(50) {
transform: rotate(300deg) translateY($arc_radius);
}
.marks li:nth-child(51) {
transform: rotate(306deg) translateY($arc_radius);
}
.marks li:nth-child(52) {
transform: rotate(312deg) translateY($arc_radius);
}
.marks li:nth-child(53) {
transform: rotate(318deg) translateY($arc_radius);
}
.marks li:nth-child(54) {
transform: rotate(324deg) translateY($arc_radius);
}
.marks li:nth-child(55) {
transform: rotate(330deg) translateY($arc_radius);
}
.marks li:nth-child(56) {
transform: rotate(336deg) translateY($arc_radius);
}
.marks li:nth-child(57) {
transform: rotate(342deg) translateY($arc_radius);
}
.marks li:nth-child(58) {
transform: rotate(348deg) translateY($arc_radius);
}
.marks li:nth-child(59) {
transform: rotate(354deg) translateY($arc_radius);
}
.marks li:nth-child(60) {
transform: rotate(360deg) translateY($arc_radius);
}
/*
Some overrides.
*/
.reactor-container.active {
$arc_radius: 130px;
$size3: 6px;
$cshadow: rgba(2, 254, 255, 0.8);
$marks_color_1: rgba(2, 254, 255, 1);
$marks_color_2: rgba(2, 254, 255, 0.3);
$colour1: rgba(2, 255, 255, 0.15);
$colour3: rgba(2, 255, 255, 0.3);
$cshadow: rgba(2, 254, 255, 0.8);
scale: 1.1;
opacity: 1;
.coil-container {
animation: 3s infinite linear reactor-anim;
}
.reactor-container-inner {
box-shadow: 0px 0px 50px 15px $colour3, inset 0px 0px 50px 15px $colour3;
}
.core-inner {
border: 5px solid #1b4e5f;
background-color: #ffffff;
box-shadow: 0px 0px 7px 5px #52fefe, 0px 0px 10px 10px #52fefe inset;
}
.core-outer {
border: 1px solid #52fefe;
background-color: #ffffff;
box-shadow: 0px 0px 2px 1px #52fefe, 0px 0px 10px 5px #52fefe inset;
}
.core-wrapper {
background-color: #073c4b;
box-shadow: 0px 0px 5px 4px #52fefe, 0px 0px 6px 2px #52fefe inset;
}
.tunnel {
background-color: #ffffff;
box-shadow: 0px 0px 5px 1px #52fefe, 0px 0px 5px 4px #52fefe inset;
}
.coil {
background-color: #073c4b;
box-shadow: 0px 0px 5px #52fefe inset;
}
.semi_arc {
border: 6px solid #02feff;
background: rgba(2, 254, 255, 0.2);
}
.e5_1 {
animation: rotate 3s linear infinite;
}
.e5_2 {
animation: rotate_anti 2s linear infinite;
}
.e5_3 {
animation: rotate 2s linear infinite;
}
.e5_4 {
animation: rotate_anti 2s linear infinite;
}
}
</style>

View File

@@ -0,0 +1,18 @@
<script>
export let no_margin = false;
</script>
<div class="h-divider" class:no-margin="{no_margin}"></div>
<style lang="scss">
.h-divider {
margin: 20px 0;
height: 40px;
background-image: url(media/images/decor.png);
background-position: center;
&.no-margin {
margin: 0;
}
}
</style>

View File

@@ -0,0 +1,11 @@
<script>
let search_q = "";
</script>
<div id="search-form" class="search" class:active={search_q != ""}>
<form action="#" method="GET">
<input bind:value={search_q} type="text" name="q" placeholder="Введите команду или скажите &laquo;Джарвис&raquo; ..." autocomplete="off" minlength="3" maxlength="30">
<button type="submit"></button>
<small>Enter</small>
</form>
</div>

View File

@@ -0,0 +1,377 @@
<script>
// IMPORTS
import { invoke } from "@tauri-apps/api/core"
import { onMount } from 'svelte'
import { capitalizeFirstLetter } from "@/functions";
// VARIABLES
let selected_microphone = 0;
let microphone_label = "";
let nn_details = {
"ww_engine": "",
"stt_engine": "Vosk"
}
// let resources_cpu_temp = 0;
// let resources_cpu_usage = 0;
let resources_ram_usage = "-";
// CODE
setInterval(() => {
(async () => {
resources_ram_usage = Number(await invoke("get_current_ram_usage")).toFixed(2);
// resources_cpu_temp = await invoke("get_cpu_temp");
// resources_cpu_usage = +Number(await invoke("get_cpu_usage")).toFixed(2);
})().catch(err => {
console.error(err);
});
}, 1000);
onMount(async () => {
(async () => {
selected_microphone = +Number(await invoke("db_read", {key: "selected_microphone"}));
microphone_label = await invoke("pv_get_audio_device_name", {idx: selected_microphone});
nn_details["ww_engine"] = capitalizeFirstLetter(await invoke("db_read", {key: "selected_wake_word_engine"}));
// resources_cpu_temp = await invoke("get_cpu_temp");
// resources_cpu_usage = +Number(await invoke("get_cpu_usage")).toFixed(2);
})().catch(err => {
console.error(err);
});
});
</script>
<div class="statistics">
<div class="online">
<div class="pulse"><div class="wave"></div></div>
<div class="info">
<span class="num">Микрофон</span>
<small title="{microphone_label}">{microphone_label}</small>
</div>
</div>
<div class="files">
<div class="pulse"><div class="wave"></div></div>
<div class="info">
<span class="num">Нейросети</span>
<small>{nn_details["ww_engine"]} + {nn_details["stt_engine"]}</small>
</div>
</div>
<div class="downloads hint--bottom" aria-label="Общее количество скачиваний по всему проекту">
<div class="pulse"><div class="wave"></div></div>
<div class="info">
<span class="num">Ресурсы</span>
<small><!-- CPU {resources_cpu_usage}%<br /> -->RAM {resources_ram_usage}mb</small>
</div>
</div>
</div>
<style lang="scss">
.statistics {
position: relative;
z-index: 3;
padding: 0 10px;
height: 100px;
display: flex;
justify-content: space-between;
& > div {
height: 70px;
}
.info {
z-index: 10;
}
& > .online {
position: relative;
width: 40%;
$base-color: rgba(0, 191, 8, 1);
$mid-color: rgba(0, 191, 8, 0.4);
$end-color: rgba(0, 191, 8, 0);
& > .pulse::before {
background-color: rgba(0, 191, 8, 1);
}
& > .pulse::after {
background-color: rgba(0, 191, 8, 1);
animation: online-cdot linear 3s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
}
& > .pulse .wave {
background-color: rgba(0, 191, 8, 0.4);
animation: online-radarWave cubic-bezier(0, 0.54, 0.53, 1) 3s 0s;
animation-iteration-count: infinite;
}
& > .pulse .wave::after {
background-color: rgba(0, 191, 8, 0.4);
animation: online-radarWave cubic-bezier(0, 0.54, 0.53, 1) 3s
0.1s;
animation-iteration-count: infinite;
}
& > .info {
position: absolute;
top: 26px;
left: 26px;
& > span.num {
font-size: 18px;
font-weight: bold;
color: #00bf08;
}
& > small {
display: block;
color: #535a60;
font-size: 12px;
position: relative;
top: 0;
width: 130px;
max-height: 40px;
overflow: hidden;
line-height: 1.5em;
}
}
@keyframes online-cdot {
0% {
opacity: 0.3;
background: $base-color;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
background: $end-color;
}
}
@keyframes online-radarWave {
0% {
opacity: 0.1;
transform: scale(0);
}
5% {
background: $mid-color;
opacity: 1;
}
100% {
transform: scale(1.2);
background: $end-color;
}
}
}
& > .files {
position: relative;
width: 35%;
$base-color: rgba(255, 129, 48, 1);
$mid-color: rgba(255, 129, 48, 0.4);
$end-color: rgba(255, 129, 48, 0);
& > .pulse::before {
background-color: $base-color;
}
& > .pulse::after {
background-color: $base-color;
animation: files-cdot linear 5s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
}
& > .pulse .wave {
background-color: $mid-color;
animation: files-radarWave cubic-bezier(0, 0.54, 0.53, 1) 5s 0s;
animation-iteration-count: infinite;
}
& > .pulse .wave::after {
background-color: $mid-color;
animation: files-radarWave cubic-bezier(0, 0.54, 0.53, 1) 5s
0.1s;
animation-iteration-count: infinite;
}
& > .info {
position: absolute;
top: 26px;
left: 26px;
& > span.num {
font-size: 18px;
font-weight: bold;
color: #ff8130;
}
& > small {
display: block;
color: #535a60;
font-size: 12px;
position: relative;
top: 0;
}
}
@keyframes files-cdot {
0% {
opacity: 0.3;
background: $base-color;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
background: $end-color;
}
}
@keyframes files-radarWave {
0% {
opacity: 0.1;
transform: scale(0);
}
5% {
background: $mid-color;
transform: scale(0.2);
opacity: 1;
}
100% {
transform: scale(0.8);
background: $end-color;
}
}
}
& > .downloads {
position: relative;
$base-color: rgba(11,66,166, 1);
$mid-color: rgba(32, 150, 243, 0.4);
$end-color: rgba(32, 150, 243, 0);
& > .pulse::before {
background: rgba(32, 150, 243, 1);
}
& > .pulse::after {
background: rgba(32, 150, 243, 1);
animation: downloads-cdot linear 7s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
animation-delay: 1s;
}
& > .pulse .wave {
background-color: $mid-color;
animation: downloads-radarWave cubic-bezier(0, 0.54, 0.53, 1) 7s
0s;
animation-iteration-count: infinite;
animation-delay: 1s;
}
& > .pulse .wave::after {
background-color: $mid-color;
animation: downloads-radarWave cubic-bezier(0, 0.54, 0.53, 1) 7s
0.1s;
animation-iteration-count: infinite;
animation-delay: 1s;
}
& > .info {
position: absolute;
top: 26px;
left: 26px;
& > span.num {
font-size: 18px;
font-weight: bold;
color: #1b78a6;
}
& > small {
display: block;
color: #535a60;
font-size: 12px;
position: relative;
top: 0;
}
}
@keyframes downloads-cdot {
0% {
opacity: 0.3;
background: $base-color;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
background: $end-color;
}
}
@keyframes downloads-radarWave {
0% {
opacity: 0.1;
transform: scale(0);
}
5% {
background: $mid-color;
opacity: 1;
}
100% {
transform: scale(0.7);
background: $end-color;
}
}
}
.pulse {
position: relative;
height: 100px;
width: 100px;
margin: 0;
left: -43px;
top: 0px;
z-index: 5;
}
.pulse::before {
content: '';
position: absolute;
width: 11px;
height: 11px;
border-radius: 50%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
opacity: .5;
}
.pulse::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.pulse .wave {
position: absolute;
left: 7%;
top: 7%;
width: 86%;
height: 86%;
border-radius: 50%;
opacity: 0;
}
.pulse .wave::after {
content: '';
position: absolute;
left: 7%;
top: 7%;
width: 86%;
height: 86%;
border-radius: 50%;
opacity: 0;
}
}
</style>

308
frontend/_src/css/main.scss Normal file
View File

@@ -0,0 +1,308 @@
$prim-font: "Roboto", sans-serif;
$sec-font: "Roboto Condensed", sans-serif;
html, body {
overflow-x: hidden;
}
::-webkit-scrollbar {
width: 15px;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
}
::-webkit-scrollbar-thumb {
background: -webkit-gradient(linear,left top,left bottom,from(#999),to(#27292F));
background: linear-gradient(to bottom,#999,#27292F);
}
::selection {
background: #FF8901!important; /* WebKit/Blink Browsers */
color: white!important;
text-shadow: 1px 1px 1px rgba(0, 0, 0, .5)!important;
}
::-moz-selection {
background: #FF8901!important; /* WebKit/Blink Browsers */
color: white!important;
text-shadow: 1px 1px 1px rgba(0, 0, 0, .5)!important;
}
/*
* HEADER
*/
#header {
height: 80px;
background-color: #0c1013;
box-shadow: 0 0 19px 2px rgba(0, 0, 0, 0.91);
position: relative;
z-index: 100;
text-align: justify;
display: flex;
justify-content: space-between;
.logo {
margin-top: 12px;
& > a > img {
width: 57px;
display: inline-block;
transition: .5s opacity ease-in;
&:hover {
opacity: .8;
}
}
& > div {
display: inline-block;
margin-left: 13px;
margin-top: 8px;
vertical-align: top;
}
h1 {
color: #ffffff;
font-family: $sec-font;
font-size: 20px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.1px;
margin: 0;
& > a {
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, .5);
text-decoration: none;
&:hover {
color: #8AC832;
}
}
}
h2 {
color: #888;
font-family: $sec-font;
font-size: 14px;
font-weight: bold;
letter-spacing: 0.1px;
margin-top: 2px;
letter-spacing: -0.01px;
}
}
.top-menu {
vertical-align: top;
margin-top: 18px;
margin-left: 50px;
& > ul {
margin: 0;
padding: 0;
list-style: none;
& > li {
display: inline-block;
position: relative;
&:not(:first-child) {
margin-left: 20px;
}
& > a {
display: inline-block;
text-decoration: none;
color: #c6c6c6;
font-family: $sec-font;
font-size: 16px;
text-transform: uppercase;
font-weight: bold;
transition: .2s color;
padding: 10px 0;
& > svg {
height: 20px;
width: 20px;
vertical-align: middle;
margin-bottom: 4px;
margin-right: 3px;
margin-left: 3px;
}
&:hover, &.active {
color: #8AC832;
&:after {
-webkit-transform: scaleX(1);
-ms-transform: scaleX(1);
transform: scaleX(1);
-webkit-transform-origin: left;
-ms-transform-origin: left;
transform-origin: left;
}
}
&:after {
content: "";
display: block;
height: 3px;
border-radius: 10px;
-webkit-transform: scaleX(0);
-ms-transform: scaleX(0);
transform: scaleX(0);
transition: .4s -webkit-transform;
transition: .4s transform;
-webkit-transform-origin: 100% 0;
-ms-transform-origin: 100% 0;
transform-origin: 100% 0;
background: #8AC832;
opacity: 0.90;
position: absolute;
bottom: 5px;
left: 0;
width: 100%;
z-index: 333;
}
}
}
}
}
}
.search {
display: block;
margin: 20px 0;
text-align: center;
& > form {
position: relative;
& > input {
width: 380px;
height: 38px;
box-shadow: inset 0 0 5px 1px rgba(0, 0, 0, 0.6);
border: 1px solid rgba(6, 6, 6, 0.99);
background-color: #0f1012;
outline: none;
color: #D1D1D1;
font-family: $sec-font;
font-size: 17px;
font-weight: 600;
line-height: 70.58px;
padding-left: 20px;
padding-right: 45px;
padding-right: 45px;
&::placeholder {
color: #676767;
font-weight: 400;
}
&:focus + button + small {
opacity: .3;
}
}
& > button {
position: absolute;
padding: 0;
margin: 0;
background: none;
border: none;
right: 35px;
top: 4px;
width: 30px;
height: 30px;
opacity: 1;
transition: .3s opacity;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 110%;
height: 110%;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAbCAYAAABvCO8sAAAC4ElEQVRIiaXWX4hXRRTA8c/etkwy+gNBEVRCmxFl1ouZGlEWFUQZPfVQRLAjKvSylEFBf16EiChJOxhLQY8RtA+lRZtpRv8ohUJQNMiHooKyojZNtoeZy95+/fZ3r+uBy5xz5pzznYGZc2dodHRUB1lVviUYwXkYwk84iK8wGRHb2goNtQDXYxRXdVkV9mFrRDx/osCl2IxrG75PsAv75Z1NyzsdwQosb8R+jXURsbML8AG82rDH8TI+77eyiAAppSVYg9SYXhsRW5rxVU/+Qw3Yd7ip+PrCesB7ImKNvNMDxb05pbR+NuBSvFL0L7EYH7SB+oA/Lrm7i2tTSmlVL3AYbxf9eyzDkROFNaBTWCmfYJhIKc1rAh/DuUW/HUfnCmtAp3FrMefjmRp4Kh4tE+PYO6DO6fKp7Ao9hJeKOZZSWlBhNc4ozidbaqyUr8W4/x+42aSuOYT7KtxZHF/gcEvyvDI+KF/yFW20iPgZk8W8o8KVxZjsn/IfOdbQL8OulNLTHfJ2lHFRhYuKcbB/bKs8kVLanVK6ZkBMfS8vqLCgGL/NEQjX48MB8/UVm1/hz2KceRLAfbh3wHxde2pYbmFnY+EcYVsiYm1LzKVl/KHCN8W4sUPxUxr6j7i7AwxuKOP+ykxLW4bzWxKnyziBRXirjZRSOstMx9lW4Q38XRyPt+R/hOtwF35tgzVqDhX99QpTeK441pWVzya/49OOICmlCzFWzE0RcaRuT0/hj6Jv71qwg7xbxuNKv66BR820uIuxE6edDCmltB1XFPOeiPiL/B+sZQcexgtyk96L+3X42/eAFuM1+YUHGyJiop4f7ol/Ef/Iv5TL8VlZQMiXexBoRH7hjTXcj0TEs8242V5tNxdo8wC9J5/Sfq+25bitEfut/Gp7p7dw7w5reV/e4Yay6oW4pXyD5DC2YmNEHOsXMBuwlo3lWy3v+mpcgnPku/WL3Br3yA+uNyPi+KCC/wJQGMGINsMjCwAAAABJRU5ErkJggg==");
background-repeat: no-repeat;
background-size: 75%;
background-position: center center;
transition: all .3s;
}
&:hover {
&::before {
cursor: pointer;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAbCAYAAABvCO8sAAABN2lDQ1BBZG9iZSBSR0IgKDE5OTgpAAAokZWPv0rDUBSHvxtFxaFWCOLgcCdRUGzVwYxJW4ogWKtDkq1JQ5ViEm6uf/oQjm4dXNx9AidHwUHxCXwDxamDQ4QMBYvf9J3fORzOAaNi152GUYbzWKt205Gu58vZF2aYAoBOmKV2q3UAECdxxBjf7wiA10277jTG+38yH6ZKAyNguxtlIYgK0L/SqQYxBMygn2oQD4CpTto1EE9AqZf7G1AKcv8ASsr1fBBfgNlzPR+MOcAMcl8BTB1da4Bakg7UWe9Uy6plWdLuJkEkjweZjs4zuR+HiUoT1dFRF8jvA2AxH2w3HblWtay99X/+PRHX82Vun0cIQCw9F1lBeKEuf1UYO5PrYsdwGQ7vYXpUZLs3cLcBC7dFtlqF8hY8Dn8AwMZP/fNTP8gAAAAJcEhZcwAACxMAAAsTAQCanBgAAAXRaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0MiA3OS4xNjA5MjQsIDIwMTcvMDcvMTMtMDE6MDY6MzkgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjMtMDQtMjNUMDQ6MzE6NTgrMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIzLTA0LTIzVDA0OjM0OjI3KzA1OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIzLTA0LTIzVDA0OjM0OjI3KzA1OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowZDA5NTdiMi0zYmM3LTcxNDItODcyNS01ODA3MjA2NTFlYTIiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDoxM2UwZWYxNi03OGM0LTE2NGMtODc1Mi0xYjY5OTQ1OTczMGMiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo5YjNkZTI4Yy1iOTBmLTNjNDUtYjAwNS1kNTExOTE3ZDhkNzIiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjliM2RlMjhjLWI5MGYtM2M0NS1iMDA1LWQ1MTE5MTdkOGQ3MiIgc3RFdnQ6d2hlbj0iMjAyMy0wNC0yM1QwNDozMTo1OCswNTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDowZDA5NTdiMi0zYmM3LTcxNDItODcyNS01ODA3MjA2NTFlYTIiIHN0RXZ0OndoZW49IjIwMjMtMDQtMjNUMDQ6MzQ6MjcrMDU6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4Wh528AAAC7UlEQVRIiaXWX4gXVRTA8c9v2jLJ6A8ERVAJbUakTr2YqRFl0QRRRk89FBGYqNDLUgY51PQiRERJWhhLQY8RtA+OFm2mGf2RGqEQDAvyoaigrKhNk+3h3mGnX7/9zbgeGO45555zvvfCvWdub/POpTrI6vilGMVF6OEnHMEXmCyyaldboV4LcCPWYnGXVeEQdhRZ9fypApdhG65v+D7GPhwWdjYt7HQUK7GiEfslNhRZtbcL8EG81rDH8TI+G7SyIqtAXqYp1uGRxvT6Iqu2N+OTvvyHG7DvcEv0DYT1gasiq9YJO/06urflZbpxNuAyvBr1z7EE77eBBoA/irn7o2trXqar+4Ej2Bn177Ecx04V1oBOYZVwgmEiL9N5TeATuDDqGY7PFdaATuP2aM7HMzXwTDweJ8ZxcEids4VT2RX6DV6K5lhepgsSrME50flUS41VwrUY9/8DN5vUNXu4P8Fd0XEAR1uS58XxIeGSr2yjFVn1MyajeWeCa6MxOTjlP3KioV+FfXmZFh3y9sRxUYLLonFkcGyrbM7LdH9eptcNianv5SUJFkTjtzkC4UZ8MGS+vmLzE/wZjXNPA3gI9w2Zr2tPjQgt7HwsnCNse5FV61tirozjDwm+isbNHYqf0dB/xD0dYHBTHA8nZlraclzckjgdxwkswtttpLxMzzPTcXYleBN/R8eTLfkf4gbcjV/bYI2avai/kWAKz0XHhrjy2eR3fNIRJC/TSzEWza1FVh2r29PT+CPqu7sW7CDvxPGk2K9r4HEzLe5y7MVZp0PKy3Q3ronmvUVW/UX4D9ayB4/iBaFJH8QDOvzt+0BL8LrwwoNNRVZN1PMjffEv4h/hl3I1Po0LeEW43MNAo8ILb6zhfqzIqmebcbO92m6N0OYBelc4pYNebStwRyP2W+HVVvYX7t9hLe8JO9wUV70Qt8VvmBzFDmwpsurEoIDZgLVsid8aYddLcQUuEO7WL0JrrIQH11tFVp0cVvBfVZDA+HDoxOQAAAAASUVORK5CYII=");
}
}
}
& > small {
position: absolute;
font-family: $sec-font;
font-size: 14px;
font-weight: bold;
line-height: 1.7em;
top: 8px;
right: 75px;
background-color: #D1D1D1;
color: #080C0F;
padding: 0 7px;
padding-top: 2px;
border-radius: 4px;
opacity: 0;
transition: opacity .3s;
cursor: default;
}
}
&.active {
small {
opacity: .3;
}
}
}
@media (max-width: 1364px) {
#content>.inner>section.materials>header>h1 {
font-size: 26px;
}
#content>.inner>section.materials>article>.details>h1 {
font-size: 25px;
}
#content>.inner>section.materials>article>.details>blockquote {
font-size: 17px;
line-height: 24px;
-webkit-line-clamp: 4;
}
.btn {
font-size: 16px!important;
}
#paginator>header>h1 {
font-size: 30px!important;
}
#paginator>.paginator-wrapper>.paginator-box {
transform: scale(1.5);
margin-top: 15px;
}
#paginator>.paginator-wrapper>small:first-child {
display: none;
}
#paginator>.paginator-wrapper>small:last-child {
display: none;
}
}

View File

@@ -0,0 +1,92 @@
/*
SOME DEFAULT CSS.
*/
html, body {
margin: 0;
padding: 0;
min-height: 100vh;
min-width: 100vw;
background-color: #4C5062;
color: white;
&.assist-page {
background: rgb(24,123,123);
background: -moz-radial-gradient(circle, rgba(24,123,123,0.4906337535014006) 0%, rgba(13,15,19,1) 64%);
background: -webkit-radial-gradient(circle, rgba(24,123,123,0.4906337535014006) 0%, rgba(13,15,19,1) 64%);
background: radial-gradient(circle, rgba(24,123,123,0.4906337535014006) 0%, rgba(13,15,19,1) 64%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#187b7b",endColorstr="#0d0f13",GradientType=1);
&.assist-active {
background: rgb(24,81,123);
background: -moz-radial-gradient(circle, rgba(24,81,123,0.6418942577030813) 0%, rgba(13,15,19,1) 64%);
background: -webkit-radial-gradient(circle, rgba(24,81,123,0.6418942577030813) 0%, rgba(13,15,19,1) 64%);
background: radial-gradient(circle, rgba(24,81,123,0.6418942577030813) 0%, rgba(13,15,19,1) 64%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#18517b",endColorstr="#0d0f13",GradientType=1);
}
}
}
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
color: #2A9CD0;
text-decoration: none;
font-weight: bold;
&:hover {
color: #1dabed;
text-decoration: underline;
}
}
/*
OVERRIDES.
*/
#wrapper {
padding: 0;
margin: 0;
}
#header, main {
padding: 0 30px;
}
select, input {
color: #ccc!important;
}
.form {
label {
font-weight: bold!important;
color: #8AC832!important;
font-size: 15px!important;
border-bottom: 1px dotted gray;
margin-bottom: 10px!important;
& + div {
line-height: 1.2em;
}
}
.svelteui-Tab-label {
font-size: 18px!important;
}
}

View File

@@ -0,0 +1,61 @@
import { invoke } from "@tauri-apps/api/core"
import { is_listening, isListening } from "@/stores"
import { clearInterval, clearTimeout, setInterval, setTimeout } from 'worker-timers';
// setInterval(() => {
// (async () => {
// is_listening.set(await invoke("is_listening"));
// })().catch(err => {
// console.error(err);
// });
// }, 1000);
export function startListening() {
(async () => {
invoke('start_listening')
.then((message) => {
is_listening.set(true);
})
.catch((error) => {
is_listening.set(false);
console.error(error);
// alert("Ошибка: " + error);
})
})().catch(err => {
console.error(err);
});
}
export function stopListening(callback: () => void) {
(async () => {
invoke('stop_listening')
.then((message) => {
is_listening.set(false);
if(callback) {
callback();
}
})
.catch((error) => {
console.error(error);
})
})().catch(err => {
console.error(err);
});
}
export function capitalizeFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
export function showInExplorer(path: any) {
(async () => {
invoke('show_in_folder', {path: path})
.then((message) => {})
.catch((error) => {
console.error(error);
// alert("Ошибка: " + error);
})
})().catch(err => {
console.error(err);
});
}

13
frontend/_src/main.ts Normal file
View File

@@ -0,0 +1,13 @@
// Klondike project old CSS file
import "./css/main.scss";
// App current CSS file
import "./css/styles.scss";
// deploy app
import App from "./App.svelte";
const app = new App({
target: document.getElementById("app")!,
});
export default app;

View File

@@ -0,0 +1,12 @@
<!-- _layout.svelte -->
<script>
import { Container } from '@svelteuidev/core'
import Header from '@/components/Header.svelte'
</script>
<Container fluid id="wrapper">
<Header />
<main>
<slot></slot>
</main>
</Container>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import HDivider from "@/components/elements/HDivider.svelte"
import Footer from "@/components/Footer.svelte"
import { Notification, Space } from '@svelteuidev/core';
import { InfoCircled } from 'radix-icons-svelte';
import { tg_official_link } from "@/stores";
</script>
<Space h="xl" />
<Notification title='[404] Этот раздел еще находится в разработке!' icon={InfoCircled} color='blue' withCloseButton={false}>
Тут будет список команд + полноценный редактор команд.<br>
Следите за обновлениями в <a href="{tg_official_link}" target="_blank">нашем телеграм канале</a>!
</Notification>
<div style="text-align: center;margin-top: 25px;">
<img src="/media/images/tenor.gif" alt="bruh" width="320px">
</div>
<HDivider />
<Footer />

View File

@@ -0,0 +1,71 @@
<script lang="ts">
import SearchBar from "@/components/elements/SearchBar.svelte"
import ArcReactor from "@/components/elements/ArcReactor.svelte"
import HDivider from "@/components/elements/HDivider.svelte"
import Stats from "@/components/elements/Stats.svelte"
import Footer from "@/components/Footer.svelte"
import { Notification, Space } from '@svelteuidev/core'
import { InfoCircled } from 'radix-icons-svelte'
import { onMount, onDestroy } from 'svelte'
onMount(async () => {
document.body.classList.add('assist-page');
});
onDestroy(async () => {
document.body.classList.remove('assist-page');
});
import { is_listening } from "@/stores"
let is_listening__val: boolean;
is_listening.subscribe(value => {
is_listening__val = value;
});
</script>
<HDivider />
{#if !is_listening__val}
<Notification title='Внимание!' icon={InfoCircled} color='cyan' withCloseButton={false}>
В данный момент ассистент не прослушивает команды.<br />
Пожалуйста, <a href="/settings">перейдите в настройки</a> и введите ключ Picovoice.
</Notification>
<!-- <SearchBar /> -->
{:else}
<!-- <SearchBar /> -->
<ArcReactor />
{/if}
<HDivider no_margin />
<Stats />
<Footer />
<!--
<Title order={1}>This is h1 title</Title>
<Title order={1} variant='gradient' gradient={{from: 'blue', to: 'red', deg: 45}}>This is h1 title with a twist</Title>
<Menu>
<Button slot="control" variant="gradient" gradient={{ from: 'blue', to: 'teal', deg: 50 }} radius="md" size="md">Toggle Menu</Button>
<Menu.Label>Application</Menu.Label>
<Menu.Item icon={Gear}>Settings</Menu.Item>
<Menu.Item icon={ChatBubble}>Messages</Menu.Item>
<Menu.Item icon={Camera}>Gallery</Menu.Item>
<Menu.Item icon={MagnifyingGlass}>
<svelte:fragment slot='rightSection'>
<Text size="xs" color="dimmed">⌘K</Text>
</svelte:fragment>
Search
</Menu.Item>
<Divider />
<Menu.Label>Danger zone</Menu.Label>
<Menu.Item icon={Width}>Transfer my data</Menu.Item>
<Menu.Item color="red" icon={Trash}>Delete my account</Menu.Item>
</Menu>
<Checkbox bind:checked={checked} label="I agree to sell my privacy" />
{checked}
{#if checked}
YEP!
{/if} -->

View File

@@ -0,0 +1,188 @@
<script lang="ts">
// IMPORTS
import { invoke } from "@tauri-apps/api/core"
import { goto } from '@roxi/routify'
import { onMount } from 'svelte'
import { startListening, stopListening, showInExplorer } from "@/functions";
// import { setTimeout } from 'worker-timers';
import { feedback_link, log_file_path } from "@/stores";
// COMPONENTS & STUFF
import HDivider from "@/components/elements/HDivider.svelte"
import Footer from "@/components/Footer.svelte"
import { Notification, Button, Text, Tabs, Space, Alert, Input, InputWrapper, NativeSelect } from '@svelteuidev/core';
import { Check, Mix, Cube, Code, Gear, QuestionMarkCircled, CrossCircled } from 'radix-icons-svelte';
// VARIABLES
let available_microphones: { label: string; value: number }[] = [];
let settings_saved = false;
let save_button_disabled = false;
let assistant_voice_val = ""; // shared
let selected_microphone = "";
let selected_wake_word_engine = "";
let api_key__picovoice = "";
let api_key__openai = "";
// SHARED VALUES
import { assistant_voice } from "@/stores"
assistant_voice.subscribe(value => {
assistant_voice_val = value;
});
// FUNCTIONS
async function save_settings() {
save_button_disabled = true; // disable save button for a while
settings_saved = false; // hide alert
await invoke("db_write", {key: "assistant_voice", val: assistant_voice_val});
await invoke("db_write", {key: "selected_microphone", val: selected_microphone});
await invoke("db_write", {key: "selected_wake_word_engine", val: selected_wake_word_engine});
await invoke("db_write", {key: "api_key__picovoice", val: api_key__picovoice});
await invoke("db_write", {key: "api_key__openai", val: api_key__openai});
// update shared
assistant_voice.set(assistant_voice_val);
settings_saved = true; // show alert
setTimeout(() => {
settings_saved = false; // hide alert again after N seconds
}, 5000);
setTimeout(() => {
save_button_disabled = false; // enable save button again
}, 1000);
// restart listening everytime new settings is saved
stopListening(() => {
startListening();
});
}
// CODE
onMount(async () => {
// preload some vars
let _available_microphones: Array<Number> = await invoke("pv_get_audio_devices");
Object.entries(_available_microphones).forEach(entry => {
const [k, v] = entry;
available_microphones.push({
label: String(v),
value: Number(k)
});
});
available_microphones = available_microphones; // update component options
// load values from db
// assistant_voice.set(await invoke("db_read", {key: "assistant_voice"}));
selected_microphone = await invoke("db_read", {key: "selected_microphone"});
selected_wake_word_engine = await invoke("db_read", {key: "selected_wake_word_engine"});
api_key__picovoice = await invoke("db_read", {key: "api_key__picovoice"});
api_key__openai = await invoke("db_read", {key: "api_key__openai"});
});
</script>
<Space h="xl" />
<Notification title='БЕТА версия!' icon={QuestionMarkCircled} color='blue' withCloseButton={false}>
Часть функций может работать некорректно.<br />
Сообщайте обо всех найденных багах в <a href="{feedback_link}" target="_blank">наш телеграм бот</a>.
<Space h="sm" />
<Button color="gray" radius="md" size="xs" uppercase on:click={() => {showInExplorer(log_file_path)}}>Открыть папку с логами</Button>
</Notification>
<Space h="xl" />
{#if settings_saved }
<Notification title='Настройки сохранены!' icon={Check} color='teal' on:close="{() => {settings_saved = false}}"></Notification>
<Space h="xl" />
{/if}
<Tabs class="form" color='#8AC832' position="left">
<Tabs.Tab label='Общее' icon={Gear}>
<Space h="sm" />
<NativeSelect data={[
{ label: 'Jarvis ремейк (от Хауди)', value: 'jarvis-remake' },
{ label: 'Jarvis OG (из фильмов)', value: 'jarvis-og' }
]}
label="Голос ассистента"
description="Не все команды работают со всеми звуковыми пакетами."
variant="filled"
bind:value={assistant_voice_val}
/>
</Tabs.Tab>
<Tabs.Tab label='Устройства' icon={Mix}>
<Space h="sm" />
<NativeSelect data={available_microphones}
label="Выберите микрофон"
description="Его будет слушать ассистент."
variant="filled"
bind:value={selected_microphone}
/>
</Tabs.Tab>
<Tabs.Tab label='Нейросети' icon={Cube}>
<Space h="sm" />
<NativeSelect data={[
{ label: 'Rustpotter', value: 'Rustpotter' },
{ label: 'Vosk (медленный)', value: 'Vosk' },
{ label: 'Picovoice Porcupine (требует API ключ)', value: 'Picovoice' }
]}
label="Распознавание активационной фразы (Wake Word)"
description="Выберите, какая нейросеть будет отвечать за распознавание активационной фразы."
variant="filled"
bind:value={selected_wake_word_engine}
/>
{#if selected_wake_word_engine == "picovoice"}
<Space h="sm" />
<Alert title="Внимание!" color="#868E96" variant="outline">
<Notification title='Эта нейросеть работает не у всех!' icon={CrossCircled} color='orange' withCloseButton={false}>
Мы ждем официального патча от разработчиков.
</Notification>
<Space h="sm" />
<Text size='sm' color="gray">
Введите сюда свой ключ Picovoice.<br />
Он выдается бесплатно при регистрации в <a href='https://console.picovoice.ai/' target="_blank">Picovoice Console</a>.<br>
</Text>
<Space h="sm" />
<Input icon={Code} placeholder='Ключ Picovoice' variant='filled' autocomplete="off" bind:value={api_key__picovoice}/>
</Alert>
{/if}
<Space h="xl" />
<InputWrapper label="Ключ OpenAI">
<!-- <Text size='sm' color="gray">Введите сюда свой ключ OpenAI, он требуется для работы ChatGPT.<br />Получить его можно <a href="https://chat.openai.com/auth/login" target="_blank">на официальном сайте OpenAI</a>.</Text> -->
<Text size='sm' color="gray">В данный момент ChatGPT <u>не поддерживается</u>. Он будет добавлен в ближайших обновлениях.</Text>
<Space h="sm" />
<Input icon={Code} placeholder='Ключ OpenAI' variant='filled' autocomplete="off" bind:value={api_key__openai} disabled/>
</InputWrapper>
</Tabs.Tab>
</Tabs>
<Space h="xl" />
<Button color="lime" radius="md" size="sm" uppercase ripple fullSize on:click={save_settings} disabled={save_button_disabled}>
Сохранить
</Button>
<Space h="sm" />
<Button color="gray" radius="md" size="sm" uppercase fullSize on:click={() => {$goto('/')}}>
Назад
</Button>
<HDivider />
<Footer />

34
frontend/_src/stores.ts Normal file
View File

@@ -0,0 +1,34 @@
import { invoke } from "@tauri-apps/api/core"
import { writable } from 'svelte/store'
// listen state
export const is_listening = writable(true);
let is_listening__val: boolean;
is_listening.subscribe(value => {
is_listening__val = value;
});
export function isListening() {return is_listening__val}
// assistant voice
export const assistant_voice = writable("");
(async () => {
assistant_voice.set(await invoke("db_read", {key: "assistant_voice"}));
})().catch(err => {
console.error(err);
});
// etc
export let tg_official_link = "";
export let feedback_link = "";
export let github_repository_link = "";
export let log_file_path = "";
(async () => {
tg_official_link = await invoke("get_tg_official_link")
feedback_link = await invoke("get_feedback_link")
github_repository_link = await invoke("get_repository_link")
log_file_path = await invoke("get_log_file_path")
})().catch(err => {
console.error(err);
});

2
frontend/_src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

View File

@@ -1,18 +1,11 @@
<!-- src/App.svelte -->
<script>
import { Router } from "@roxi/routify";
import routes from "../.routify/routes.default.js";
import { SvelteUIProvider } from '@svelteuidev/core';
import Events from "./Events.svelte";
/** START LISTENING **/
// import { startListening } from "./functions";
// startListening();
<script lang="ts">
import { Router } from "@roxi/routify"
import routes from "../.routify/routes.default.js"
import { SvelteUIProvider } from "@svelteuidev/core"
import Events from "./Events.svelte"
</script>
<SvelteUIProvider themeObserver='dark' withNormalizeCSS withGlobalStyles>
<SvelteUIProvider themeObserver="dark" withNormalizeCSS withGlobalStyles>
<Router {routes} />
</SvelteUIProvider>

View File

@@ -1,44 +1,34 @@
<script>
import { onMount, onDestroy } from 'svelte'
import { emit, listen } from '@tauri-apps/api/event'
<script lang="ts">
import { onMount } from "svelte"
import { listen } from "@tauri-apps/api/event"
import { invoke } from "@tauri-apps/api/core"
import { assistantVoice } from "@/stores"
import { resolveResource } from '@tauri-apps/api/path'
import {Howl, Howler} from 'howler';
let assistant_voice_val = "jarvis-og";
import { assistant_voice } from "@/stores"
import { invoke } from "@tauri-apps/api/core";
assistant_voice.subscribe(value => {
assistant_voice_val = value;
});
let voiceVal = "jarvis-og"
assistantVoice.subscribe(value => {
voiceVal = value || "jarvis-og"
})
onMount(async () => {
await listen('audio-play', async (event) => {
// event.event is the event name (useful if you want to use a single callback fn for multiple event types)
// event.payload is the payload object
// let path = await resolveResource('sound/' + (assistant_voice_val == "" ? "jarvis-remake":assistant_voice_val) + '/' + event.payload['data'] + '.wav');
// console.log(path);
// let sound = new Howl({
// src: [path],
// html5: true
// });
// audio playback event
await listen<{ data: string }>("audio-play", async (event) => {
const voice = voiceVal || "jarvis-remake"
const filename = `sound/${voice}/${event.payload.data}.wav`
// sound.play();
try {
await invoke("play_sound", { filename, sleep: true })
} catch (err) {
console.error("failed to play sound:", err)
}
})
let filename = 'sound/' + (assistant_voice_val == "" ? "jarvis-remake":assistant_voice_val) + '/' + event.payload['data'] + '.wav';
await invoke("play_sound", {
filename: filename,
sleep: true
});
});
// assistant state events
await listen("assistant-greet", () => {
document.getElementById("arc-reactor")?.classList.add("active")
})
await listen('assistant-greet', (event) => {
document.getElementById("arc-reactor")?.classList.add("active");
});
await listen('assistant-waiting', (event) => {
document.getElementById("arc-reactor")?.classList.remove("active");
});
});
await listen("assistant-waiting", () => {
document.getElementById("arc-reactor")?.classList.remove("active")
})
})
</script>

View File

@@ -1,25 +1,43 @@
<script>
<script lang="ts">
import { onMount } from "svelte"
import { invoke } from "@tauri-apps/api/core"
import { appInfo } from "@/stores"
import { tg_official_link, github_repository_link } from "@/stores";
let authorName = ""
let tgLink = ""
let repoLink = ""
let current_year = new Date().getFullYear();
let author_name = "";
const currentYear = new Date().getFullYear()
(async () => {
author_name = await invoke("get_author_name")
appInfo.subscribe(info => {
tgLink = info.tgOfficialLink
repoLink = info.repositoryLink
})
})().catch(err => {
console.error(err);
});
onMount(async () => {
try {
authorName = await invoke<string>("get_author_name")
} catch (err) {
console.error("failed to get author name:", err)
}
})
</script>
<footer id="footer">
<p>© {current_year}. Автор проекта: {author_name}</p>
<p style="margin-top: 5px;margin-bottom: 15px;">
<a href="{tg_official_link}" target="_blank" class="special-link"><img src="/media/icons/howdy-logo.png" alt="" width="20px">&nbsp;&nbsp;Наш телеграм</a> канал.
<p>© {currentYear}. Автор проекта: {authorName}</p>
<p class="links">
<a href={tgLink} target="_blank" class="special-link">
<img src="/media/icons/howdy-logo.png" alt="Telegram" width="20px" />
&nbsp;&nbsp;Наш телеграм
</a>
канал.
&nbsp;&nbsp;
<a href="{github_repository_link}" target="_blank"><img src="/media/icons/github-logo.png" alt="" width="18px">&nbsp;Github репозиторий</a> проекта.</p>
<a href={repoLink} target="_blank">
<img src="/media/icons/github-logo.png" alt="GitHub" width="18px" />
&nbsp;Github репозиторий
</a>
проекта.
</p>
</footer>
<style lang="scss">
@@ -34,16 +52,21 @@
p {
margin: 0;
padding: 0;
&.links {
margin-top: 5px;
margin-bottom: 15px;
}
}
a {
color: #185876;
text-decoration: none;
transition: opacity .5s;
transition: opacity 0.5s;
img {
opacity: .5;
transition: opacity .5s;
opacity: 0.5;
transition: opacity 0.5s;
margin-top: -4px;
}
@@ -61,11 +84,9 @@
&:hover {
color: #FF07FC;
background: url(/media/images/bg/bg24.gif);
background-repeat: no-repeat;
background-size: contain;
}
}
}

View File

@@ -1,28 +1,54 @@
<script lang="ts">
import { onMount } from "svelte"
import { invoke } from "@tauri-apps/api/core"
import { Dashboard, Gear } from 'radix-icons-svelte'
import {isActive} from '@roxi/routify'
import { isActive } from "@roxi/routify"
import { Dashboard, Gear } from "radix-icons-svelte"
let app_version = "";
let appVersion = ""
(async () => {
app_version = await invoke("get_app_version")
})().catch(err => {
console.error(err);
});
onMount(async () => {
try {
appVersion = await invoke<string>("get_app_version")
} catch (err) {
console.error("failed to get app version:", err)
}
})
</script>
<header id="header">
<div class="logo">
<a href="/" title="Проект канала Хауди Хо!"><img src="/media/header-logo.png" alt=""></a>
<a href="/" title="Проект канала Хауди Хо!">
<img src="/media/header-logo.png" alt="Jarvis Logo" />
</a>
<div>
<h1><a href="/">JARVIS</a></h1>
<h2>v{app_version} <small style="color: #8AC832;opacity: .9;font-size: 13px;">BETA</small></h2>
<h2>
v{appVersion}
<small class="beta-badge">BETA</small>
</h2>
</div>
</div>
<nav class="top-menu">
<ul>
<li><a href="/commands" class:active={$isActive('/commands')}><Dashboard /> Команды</a></li>
<li><a href="/settings" class:active={$isActive('/settings')}><Gear /> Настройки</a></li>
<li>
<a href="/commands" class:active={$isActive("/commands")}>
<Dashboard /> Команды
</a>
</li>
<li>
<a href="/settings" class:active={$isActive("/settings")}>
<Gear /> Настройки
</a>
</li>
</ul>
</nav>
</header>
<style lang="scss">
.beta-badge {
color: #8AC832;
opacity: 0.9;
font-size: 13px;
}
</style>

View File

@@ -1,5 +1,4 @@
<nav>
<a href="/index">Main Page</a>
<a href="/">Main Page</a>
<a href="/settings">Настройки</a>
</nav>

View File

@@ -1,65 +1,54 @@
<!-- Based on: https://github.com/rembertdesigns/Iron-Man-Arc-Reactor-Pure-CSS and https://codepen.io/FlyingEmu/pen/DZNqEj -->
<!-- Based on: https://github.com/rembertdesigns/Iron-Man-Arc-Reactor-Pure-CSS -->
<!-- and https://codepen.io/FlyingEmu/pen/DZNqEj -->
<div id="arc-reactor" class="reactor-container">
<div class="reactor-container-inner circle abs-center">
<ul class="marks"><li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li></ul>
<ul class="marks">
{#each Array(60) as _, i}
<li></li>
{/each}
</ul>
<div class="e7">
<div class="semi_arc_3 e5_1">
<div class="semi_arc_3 e5_2">
<div class="semi_arc_3 e5_3">
<div class="semi_arc_3 e5_4" />
<div class="semi_arc_3 e5_4"></div>
</div>
</div>
</div>
</div>
</div>
<div class="tunnel circle abs-center" />
<div class="core-wrapper circle abs-center" />
<div class="core-outer circle abs-center" />
<div class="core-inner circle abs-center" />
<div class="tunnel circle abs-center"></div>
<div class="core-wrapper circle abs-center"></div>
<div class="core-outer circle abs-center"></div>
<div class="core-inner circle abs-center"></div>
<div class="coil-container">
<div class="coil coil-1" />
<div class="coil coil-2" />
<div class="coil coil-3" />
<div class="coil coil-4" />
<div class="coil coil-5" />
<div class="coil coil-6" />
<div class="coil coil-7" />
<div class="coil coil-8" />
{#each Array(8) as _, i}
<div class="coil coil-{i + 1}"></div>
{/each}
</div>
</div>
<style lang="scss" global>
$arc_radius: 130px;
// [ Variables ]--
$arc-radius: 130px;
$size3: 6px;
$cshadow: rgba(2, 254, 255, 0.8);
$marks_color_1: rgba(2, 254, 255, 1);
$marks_color_2: rgba(2, 254, 255, 0.3);
$marks-color-1: rgba(2, 254, 255, 1);
$marks-color-2: rgba(2, 254, 255, 0.3);
$colour1: rgba(2, 255, 255, 0.15);
$colour3: rgba(2, 255, 255, 0.3);
$cshadow: rgba(2, 254, 255, 0.8);
// [ Base container ]--
.reactor-container {
width: 300px;
height: 320px;
margin: auto;
// border: 1px dashed #888;
position: relative;
border-radius: 50%;
transition: scale 1s ease, opacity .5s ease;
transition: scale 1s ease, opacity 0.5s ease;
scale: 0.9;
opacity: .9;
// background-color: #384c50;
// border: 1px solid #121414;
// box-shadow: 0px 0px 32px 8px #121414, 0px 0px 4px 1px #121414 inset;
opacity: 0.9;
ul {
list-style: none;
@@ -67,16 +56,19 @@
padding: 0;
}
}
.reactor-container-inner {
height: 238px;
width: 238px;
background-color: #161a1b;
box-shadow: 0px 0px 50px 15px $colour3, inset 0px 0px 50px 15px $colour3;
// box-shadow: 0px 0px 4px 1px #52fefe;
}
// [ Utility classes ]--
.circle {
border-radius: 50%;
}
.abs-center {
position: absolute;
top: 0;
@@ -85,6 +77,8 @@
left: 0;
margin: auto;
}
// [ Core elements ]--
.core-inner {
width: 70px;
height: 70px;
@@ -92,6 +86,7 @@
background-color: #ffffff;
box-shadow: 0px 0px 7px 5px #52fefe, 0px 0px 10px 10px #52fefe inset;
}
.core-outer {
width: 120px;
height: 120px;
@@ -99,18 +94,22 @@
background-color: #ffffff;
box-shadow: 0px 0px 2px 1px #52fefe, 0px 0px 10px 5px #52fefe inset;
}
.core-wrapper {
width: 180px;
height: 180px;
background-color: #073c4b;
box-shadow: 0px 0px 5px 4px #52fefe, 0px 0px 6px 2px #52fefe inset;
}
.tunnel {
width: 220px;
height: 220px;
background-color: #ffffff;
box-shadow: 0px 0px 5px 1px #52fefe, 0px 0px 5px 4px #52fefe inset;
}
// [ Coil animation ]--
.coil-container {
position: relative;
width: 100%;
@@ -118,6 +117,7 @@
animation: 10s infinite linear reactor-anim;
transition: animation 1s;
}
.coil {
position: absolute;
width: 30px;
@@ -128,43 +128,19 @@
background-color: #073c4b;
box-shadow: 0px 0px 5px #52fefe inset;
}
.coil-1 {
transform: rotate(0deg);
@for $i from 1 through 8 {
.coil-#{$i} {
transform: rotate(#{($i - 1) * 45}deg);
}
.coil-2 {
transform: rotate(45deg);
}
.coil-3 {
transform: rotate(90deg);
}
.coil-4 {
transform: rotate(135deg);
}
.coil-5 {
transform: rotate(180deg);
}
.coil-6 {
transform: rotate(225deg);
}
.coil-7 {
transform: rotate(270deg);
}
.coil-8 {
transform: rotate(315deg);
}
@keyframes reactor-anim {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@mixin border-radius($pixel...) {
border-radius: $pixel;
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
// [ Arc element ]--
.e7 {
position: relative;
z-index: 1;
@@ -177,66 +153,11 @@
margin: auto;
border: $size3 solid transparent;
background: transparent;
@include border-radius(50%);
transform: rotateZ(0deg);
transition: box-shadow 3s ease;
text-align: center;
opacity: .3;
}
.semi_arc {
width: 100px;
height: 100px;
border: 6px solid #02feff;
background: rgba(2, 254, 255, 0.2);
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
transform: rotateZ(0deg);
transition: box-shadow 3s ease;
text-align: center;
overflow: hidden;
}
.semi_arc:hover {
box-shadow: 0px 0px 30px $cshadow;
transition: 0.3s;
}
.semi_arc_2 {
content: "";
position: absolute;
width: 94%;
height: 94%;
left: 3%;
top: 3%;
border: 5px solid #02feff;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
animation: rotate 4s linear infinite;
text-align: center;
overflow: hidden;
}
.semi_arc_2:after {
content: "";
position: absolute;
width: 94%;
height: 94%;
left: 3%;
top: 3%;
border: 4px solid #02feff;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
animation: rotate_anti 2s linear infinite;
opacity: 0.3;
}
.semi_arc_3 {
@@ -247,132 +168,27 @@
left: 3%;
top: 3%;
border: 5px solid #02feff;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
animation: rotate 4s linear infinite;
text-align: center;
overflow: hidden;
}
.e1:after {
border-color: rgba(2, 255, 255, 0.6);
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
.e2:after {
border-color: rgba(2, 255, 255, 0.6);
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid transparent;
}
.e3 {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
animation: rotate 5s linear infinite;
}
.e3:after {
border-color: rgba(2, 255, 255, 0.6);
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
}
.e4 {
width: 150px;
height: 150px;
}
.e4_1 {
border-color: rgba(2, 255, 255, 0.3);
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
.e4_1:after {
border-color: rgba(2, 255, 255, 0.6);
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
}
.e5 {
width: 200px;
height: 200px;
}
.e5_1 {
color: rgba(2, 255, 255, 0.15);
border: 2px solid;
border-left: 2px solid transparent;
animation: rotate 5s linear infinite;
}
.e5_2 {
color: rgba(2, 255, 255, 0.7);
border: 4px solid;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
animation: rotate_anti 4s linear infinite;
}
.e5_3 {
color: rgba(2, 255, 255, 0.5);
border: 2px solid;
border-left: 2px solid transparent;
border-right: 2px solid transparent;
animation: rotate 3s linear infinite;
}
.e5_4 {
color: rgba(2, 255, 255, 0.15);
border: 4px solid;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid transparent;
animation: rotate_anti 2s linear infinite;
}
.e6 {
border-color: transparent;
background: rgba(255, 255, 255, 0);
width: 200px;
height: 200px;
@keyframes rotate {
0% { transform: rotateZ(0deg); }
100% { transform: rotateZ(360deg); }
}
@keyframes rotate_anti {
0% {
transform: rotateZ(360deg);
}
100% {
transform: rotateZ(0deg);
}
}
@keyframes rotate {
0% {
transform: rotateZ(0deg);
}
100% {
transform: rotateZ(360deg);
}
0% { transform: rotateZ(0deg); }
100% { transform: rotateZ(-360deg); }
}
// [ Marks ]--
.marks {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
li {
display: block;
width: 3px;
width: 11px;
height: 11px;
background: $cshadow;
position: absolute;
@@ -383,213 +199,23 @@
}
@keyframes colour_ease2 {
0% {
background: $marks_color_1;
0% { background: $marks-color-1; }
50% { background: $marks-color-2; }
100% { background: $marks-color-1; }
}
50% {
background: $marks_color_2;
}
100% {
background: $marks_color_1;
// generate mark rotations
@for $i from 1 through 60 {
.marks li:nth-child(#{$i}) {
transform: rotate(#{$i * 6}deg) translateY($arc-radius);
}
}
.marks li:first-child {
transform: rotate(6deg) translateY($arc_radius);
}
.marks li:nth-child(2) {
transform: rotate(12deg) translateY($arc_radius);
}
.marks li:nth-child(3) {
transform: rotate(18deg) translateY($arc_radius);
}
.marks li:nth-child(4) {
transform: rotate(24deg) translateY($arc_radius);
}
.marks li:nth-child(5) {
transform: rotate(30deg) translateY($arc_radius);
}
.marks li:nth-child(6) {
transform: rotate(36deg) translateY($arc_radius);
}
.marks li:nth-child(7) {
transform: rotate(42deg) translateY($arc_radius);
}
.marks li:nth-child(8) {
transform: rotate(48deg) translateY($arc_radius);
}
.marks li:nth-child(9) {
transform: rotate(54deg) translateY($arc_radius);
}
.marks li:nth-child(10) {
transform: rotate(60deg) translateY($arc_radius);
}
.marks li:nth-child(11) {
transform: rotate(66deg) translateY($arc_radius);
}
.marks li:nth-child(12) {
transform: rotate(72deg) translateY($arc_radius);
}
.marks li:nth-child(13) {
transform: rotate(78deg) translateY($arc_radius);
}
.marks li:nth-child(14) {
transform: rotate(84deg) translateY($arc_radius);
}
.marks li:nth-child(15) {
transform: rotate(90deg) translateY($arc_radius);
}
.marks li:nth-child(16) {
transform: rotate(96deg) translateY($arc_radius);
}
.marks li:nth-child(17) {
transform: rotate(102deg) translateY($arc_radius);
}
.marks li:nth-child(18) {
transform: rotate(108deg) translateY($arc_radius);
}
.marks li:nth-child(19) {
transform: rotate(114deg) translateY($arc_radius);
}
.marks li:nth-child(20) {
transform: rotate(120deg) translateY($arc_radius);
}
.marks li:nth-child(21) {
transform: rotate(126deg) translateY($arc_radius);
}
.marks li:nth-child(22) {
transform: rotate(132deg) translateY($arc_radius);
}
.marks li:nth-child(23) {
transform: rotate(138deg) translateY($arc_radius);
}
.marks li:nth-child(24) {
transform: rotate(144deg) translateY($arc_radius);
}
.marks li:nth-child(25) {
transform: rotate(150deg) translateY($arc_radius);
}
.marks li:nth-child(26) {
transform: rotate(156deg) translateY($arc_radius);
}
.marks li:nth-child(27) {
transform: rotate(162deg) translateY($arc_radius);
}
.marks li:nth-child(28) {
transform: rotate(168deg) translateY($arc_radius);
}
.marks li:nth-child(29) {
transform: rotate(174deg) translateY($arc_radius);
}
.marks li:nth-child(30) {
transform: rotate(180deg) translateY($arc_radius);
}
.marks li:nth-child(31) {
transform: rotate(186deg) translateY($arc_radius);
}
.marks li:nth-child(32) {
transform: rotate(192deg) translateY($arc_radius);
}
.marks li:nth-child(33) {
transform: rotate(198deg) translateY($arc_radius);
}
.marks li:nth-child(34) {
transform: rotate(204deg) translateY($arc_radius);
}
.marks li:nth-child(35) {
transform: rotate(210deg) translateY($arc_radius);
}
.marks li:nth-child(36) {
transform: rotate(216deg) translateY($arc_radius);
}
.marks li:nth-child(37) {
transform: rotate(222deg) translateY($arc_radius);
}
.marks li:nth-child(38) {
transform: rotate(228deg) translateY($arc_radius);
}
.marks li:nth-child(39) {
transform: rotate(234deg) translateY($arc_radius);
}
.marks li:nth-child(40) {
transform: rotate(240deg) translateY($arc_radius);
}
.marks li:nth-child(41) {
transform: rotate(246deg) translateY($arc_radius);
}
.marks li:nth-child(42) {
transform: rotate(252deg) translateY($arc_radius);
}
.marks li:nth-child(43) {
transform: rotate(258deg) translateY($arc_radius);
}
.marks li:nth-child(44) {
transform: rotate(264deg) translateY($arc_radius);
}
.marks li:nth-child(45) {
transform: rotate(270deg) translateY($arc_radius);
}
.marks li:nth-child(46) {
transform: rotate(276deg) translateY($arc_radius);
}
.marks li:nth-child(47) {
transform: rotate(282deg) translateY($arc_radius);
}
.marks li:nth-child(48) {
transform: rotate(288deg) translateY($arc_radius);
}
.marks li:nth-child(49) {
transform: rotate(294deg) translateY($arc_radius);
}
.marks li:nth-child(50) {
transform: rotate(300deg) translateY($arc_radius);
}
.marks li:nth-child(51) {
transform: rotate(306deg) translateY($arc_radius);
}
.marks li:nth-child(52) {
transform: rotate(312deg) translateY($arc_radius);
}
.marks li:nth-child(53) {
transform: rotate(318deg) translateY($arc_radius);
}
.marks li:nth-child(54) {
transform: rotate(324deg) translateY($arc_radius);
}
.marks li:nth-child(55) {
transform: rotate(330deg) translateY($arc_radius);
}
.marks li:nth-child(56) {
transform: rotate(336deg) translateY($arc_radius);
}
.marks li:nth-child(57) {
transform: rotate(342deg) translateY($arc_radius);
}
.marks li:nth-child(58) {
transform: rotate(348deg) translateY($arc_radius);
}
.marks li:nth-child(59) {
transform: rotate(354deg) translateY($arc_radius);
}
.marks li:nth-child(60) {
transform: rotate(360deg) translateY($arc_radius);
}
/*
Some overrides.
*/
// [ Active state ]--
.reactor-container.active {
$arc_radius: 130px;
$size3: 6px;
$cshadow: rgba(2, 254, 255, 0.8);
$marks_color_1: rgba(2, 254, 255, 1);
$marks_color_2: rgba(2, 254, 255, 0.3);
$colour1: rgba(2, 255, 255, 0.15);
$colour3: rgba(2, 255, 255, 0.3);
$cshadow: rgba(2, 254, 255, 0.8);
scale: 1.1;
opacity: 1;
.coil-container {
animation: 3s infinite linear reactor-anim;
}
@@ -598,50 +224,9 @@
box-shadow: 0px 0px 50px 15px $colour3, inset 0px 0px 50px 15px $colour3;
}
.core-inner {
border: 5px solid #1b4e5f;
background-color: #ffffff;
box-shadow: 0px 0px 7px 5px #52fefe, 0px 0px 10px 10px #52fefe inset;
.e5_1 { animation: rotate 3s linear infinite; }
.e5_2 { animation: rotate_anti 2s linear infinite; }
.e5_3 { animation: rotate 2s linear infinite; }
.e5_4 { animation: rotate_anti 2s linear infinite; }
}
.core-outer {
border: 1px solid #52fefe;
background-color: #ffffff;
box-shadow: 0px 0px 2px 1px #52fefe, 0px 0px 10px 5px #52fefe inset;
}
.core-wrapper {
background-color: #073c4b;
box-shadow: 0px 0px 5px 4px #52fefe, 0px 0px 6px 2px #52fefe inset;
}
.tunnel {
background-color: #ffffff;
box-shadow: 0px 0px 5px 1px #52fefe, 0px 0px 5px 4px #52fefe inset;
}
.coil {
background-color: #073c4b;
box-shadow: 0px 0px 5px #52fefe inset;
}
.semi_arc {
border: 6px solid #02feff;
background: rgba(2, 254, 255, 0.2);
}
.e5_1 {
animation: rotate 3s linear infinite;
}
.e5_2 {
animation: rotate_anti 2s linear infinite;
}
.e5_3 {
animation: rotate 2s linear infinite;
}
.e5_4 {
animation: rotate_anti 2s linear infinite;
}
}
</style>

View File

@@ -1,14 +1,14 @@
<script>
export let no_margin = false;
<script lang="ts">
export let noMargin = false
</script>
<div class="h-divider" class:no-margin="{no_margin}"></div>
<div class="h-divider" class:no-margin={noMargin}></div>
<style lang="scss">
.h-divider {
margin: 20px 0;
height: 40px;
background-image: url(media/images/decor.png);
background-image: url(/media/images/decor.png);
background-position: center;
&.no-margin {

View File

@@ -1,11 +1,19 @@
<script>
let search_q = "";
<script lang="ts">
let searchQuery = ""
</script>
<div id="search-form" class="search" class:active={search_q != ""}>
<div id="search-form" class="search" class:active={searchQuery !== ""}>
<form action="#" method="GET">
<input bind:value={search_q} type="text" name="q" placeholder="Введите команду или скажите &laquo;Джарвис&raquo; ..." autocomplete="off" minlength="3" maxlength="30">
<button type="submit"></button>
<input
bind:value={searchQuery}
type="text"
name="q"
placeholder="Введите команду или скажите «Джарвис» ..."
autocomplete="off"
minlength="3"
maxlength="30"
/>
<button type="submit" aria-label="Search"></button>
<small>Enter</small>
</form>
</div>

View File

@@ -1,46 +1,46 @@
<script>
// IMPORTS
<script lang="ts">
import { onMount, onDestroy } from "svelte"
import { invoke } from "@tauri-apps/api/core"
import { onMount } from 'svelte'
import { capitalizeFirstLetter } from "@/functions";
import { capitalizeFirstLetter } from "@/functions"
// VARIABLES
let selected_microphone = 0;
let microphone_label = "";
let microphoneLabel = ""
let wakeWordEngine = ""
let sttEngine = "Vosk"
let ramUsage = "-"
let nn_details = {
"ww_engine": "",
"stt_engine": "Vosk"
let interval: number | null = null
async function updateRamUsage() {
try {
const usage = await invoke<number>("get_current_ram_usage")
ramUsage = usage.toFixed(2)
} catch (err) {
console.error("failed to get ram usage:", err)
}
}
// let resources_cpu_temp = 0;
// let resources_cpu_usage = 0;
let resources_ram_usage = "-";
// CODE
setInterval(() => {
(async () => {
resources_ram_usage = Number(await invoke("get_current_ram_usage")).toFixed(2);
// resources_cpu_temp = await invoke("get_cpu_temp");
// resources_cpu_usage = +Number(await invoke("get_cpu_usage")).toFixed(2);
})().catch(err => {
console.error(err);
});
}, 1000);
onMount(async () => {
(async () => {
selected_microphone = +Number(await invoke("db_read", {key: "selected_microphone"}));
microphone_label = await invoke("pv_get_audio_device_name", {idx: selected_microphone});
// start polling ram usage
interval = setInterval(updateRamUsage, 1000) as unknown as number
nn_details["ww_engine"] = capitalizeFirstLetter(await invoke("db_read", {key: "selected_wake_word_engine"}));
try {
// load microphone info
const micIndex = Number(await invoke<string>("db_read", { key: "selected_microphone" }))
microphoneLabel = await invoke<string>("pv_get_audio_device_name", { idx: micIndex })
// resources_cpu_temp = await invoke("get_cpu_temp");
// resources_cpu_usage = +Number(await invoke("get_cpu_usage")).toFixed(2);
})().catch(err => {
console.error(err);
});
});
// load wake word engine
const engine = await invoke<string>("db_read", { key: "selected_wake_word_engine" })
wakeWordEngine = capitalizeFirstLetter(engine)
} catch (err) {
console.error("failed to load stats:", err)
}
})
onDestroy(() => {
if (interval) {
clearInterval(interval)
}
})
</script>
<div class="statistics">
@@ -48,21 +48,23 @@
<div class="pulse"><div class="wave"></div></div>
<div class="info">
<span class="num">Микрофон</span>
<small title="{microphone_label}">{microphone_label}</small>
<small title={microphoneLabel}>{microphoneLabel}</small>
</div>
</div>
<div class="files">
<div class="pulse"><div class="wave"></div></div>
<div class="info">
<span class="num">Нейросети</span>
<small>{nn_details["ww_engine"]} + {nn_details["stt_engine"]}</small>
<small>{wakeWordEngine} + {sttEngine}</small>
</div>
</div>
<div class="downloads hint--bottom" aria-label="Общее количество скачиваний по всему проекту">
<div class="pulse"><div class="wave"></div></div>
<div class="info">
<span class="num">Ресурсы</span>
<small><!-- CPU {resources_cpu_usage}%<br /> -->RAM {resources_ram_usage}mb</small>
<small>RAM {ramUsage}mb</small>
</div>
</div>
</div>
@@ -84,6 +86,7 @@
z-index: 10;
}
// [ Online/Microphone stat ]--
& > .online {
position: relative;
width: 40%;
@@ -93,24 +96,22 @@
$end-color: rgba(0, 191, 8, 0);
& > .pulse::before {
background-color: rgba(0, 191, 8, 1);
background-color: $base-color;
}
& > .pulse::after {
background-color: rgba(0, 191, 8, 1);
animation: online-cdot linear 3s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
background-color: $base-color;
animation: online-cdot linear 3s infinite forwards;
}
& > .pulse .wave {
background-color: rgba(0, 191, 8, 0.4);
animation: online-radarWave cubic-bezier(0, 0.54, 0.53, 1) 3s 0s;
animation-iteration-count: infinite;
background-color: $mid-color;
animation: online-radarWave cubic-bezier(0, 0.54, 0.53, 1) 3s 0s infinite;
}
& > .pulse .wave::after {
background-color: rgba(0, 191, 8, 0.4);
animation: online-radarWave cubic-bezier(0, 0.54, 0.53, 1) 3s
0.1s;
animation-iteration-count: infinite;
background-color: $mid-color;
animation: online-radarWave cubic-bezier(0, 0.54, 0.53, 1) 3s 0.1s infinite;
}
& > .info {
@@ -123,6 +124,7 @@
font-weight: bold;
color: #00bf08;
}
& > small {
display: block;
color: #535a60;
@@ -137,34 +139,19 @@
}
@keyframes online-cdot {
0% {
opacity: 0.3;
background: $base-color;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
background: $end-color;
}
0% { opacity: 0.3; background: $base-color; }
50% { opacity: 0.5; }
100% { opacity: 1; background: $end-color; }
}
@keyframes online-radarWave {
0% {
opacity: 0.1;
transform: scale(0);
}
5% {
background: $mid-color;
opacity: 1;
}
100% {
transform: scale(1.2);
background: $end-color;
}
0% { opacity: 0.1; transform: scale(0); }
5% { background: $mid-color; opacity: 1; }
100% { transform: scale(1.2); background: $end-color; }
}
}
// [ Files/Neural networks stat ]--
& > .files {
position: relative;
width: 35%;
@@ -176,22 +163,20 @@
& > .pulse::before {
background-color: $base-color;
}
& > .pulse::after {
background-color: $base-color;
animation: files-cdot linear 5s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
animation: files-cdot linear 5s infinite forwards;
}
& > .pulse .wave {
background-color: $mid-color;
animation: files-radarWave cubic-bezier(0, 0.54, 0.53, 1) 5s 0s;
animation-iteration-count: infinite;
animation: files-radarWave cubic-bezier(0, 0.54, 0.53, 1) 5s 0s infinite;
}
& > .pulse .wave::after {
background-color: $mid-color;
animation: files-radarWave cubic-bezier(0, 0.54, 0.53, 1) 5s
0.1s;
animation-iteration-count: infinite;
animation: files-radarWave cubic-bezier(0, 0.54, 0.53, 1) 5s 0.1s infinite;
}
& > .info {
@@ -204,6 +189,7 @@
font-weight: bold;
color: #ff8130;
}
& > small {
display: block;
color: #535a60;
@@ -214,64 +200,45 @@
}
@keyframes files-cdot {
0% {
opacity: 0.3;
background: $base-color;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
background: $end-color;
}
0% { opacity: 0.3; background: $base-color; }
50% { opacity: 0.5; }
100% { opacity: 1; background: $end-color; }
}
@keyframes files-radarWave {
0% {
opacity: 0.1;
transform: scale(0);
}
5% {
background: $mid-color;
transform: scale(0.2);
opacity: 1;
}
100% {
transform: scale(0.8);
background: $end-color;
}
0% { opacity: 0.1; transform: scale(0); }
5% { background: $mid-color; transform: scale(0.2); opacity: 1; }
100% { transform: scale(0.8); background: $end-color; }
}
}
// [ Downloads/Resources stat ]--
& > .downloads {
position: relative;
$base-color: rgba(11,66,166, 1);
$base-color: rgba(11, 66, 166, 1);
$mid-color: rgba(32, 150, 243, 0.4);
$end-color: rgba(32, 150, 243, 0);
& > .pulse::before {
background: rgba(32, 150, 243, 1);
}
& > .pulse::after {
background: rgba(32, 150, 243, 1);
animation: downloads-cdot linear 7s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
animation: downloads-cdot linear 7s infinite forwards;
animation-delay: 1s;
}
& > .pulse .wave {
background-color: $mid-color;
animation: downloads-radarWave cubic-bezier(0, 0.54, 0.53, 1) 7s
0s;
animation-iteration-count: infinite;
animation: downloads-radarWave cubic-bezier(0, 0.54, 0.53, 1) 7s 0s infinite;
animation-delay: 1s;
}
& > .pulse .wave::after {
background-color: $mid-color;
animation: downloads-radarWave cubic-bezier(0, 0.54, 0.53, 1) 7s
0.1s;
animation-iteration-count: infinite;
animation: downloads-radarWave cubic-bezier(0, 0.54, 0.53, 1) 7s 0.1s infinite;
animation-delay: 1s;
}
@@ -296,34 +263,19 @@
}
@keyframes downloads-cdot {
0% {
opacity: 0.3;
background: $base-color;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
background: $end-color;
}
0% { opacity: 0.3; background: $base-color; }
50% { opacity: 0.5; }
100% { opacity: 1; background: $end-color; }
}
@keyframes downloads-radarWave {
0% {
opacity: 0.1;
transform: scale(0);
}
5% {
background: $mid-color;
opacity: 1;
}
100% {
transform: scale(0.7);
background: $end-color;
}
0% { opacity: 0.1; transform: scale(0); }
5% { background: $mid-color; opacity: 1; }
100% { transform: scale(0.7); background: $end-color; }
}
}
// [ Shared pulse styles ]--
.pulse {
position: relative;
height: 100px;
@@ -333,8 +285,9 @@
top: 0px;
z-index: 5;
}
.pulse::before {
content: '';
content: "";
position: absolute;
width: 11px;
height: 11px;
@@ -342,10 +295,11 @@
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
opacity: .5;
opacity: 0.5;
}
.pulse::after {
content: '';
content: "";
position: absolute;
width: 20px;
height: 20px;
@@ -354,6 +308,7 @@
top: 50%;
transform: translate(-50%, -50%);
}
.pulse .wave {
position: absolute;
left: 7%;
@@ -363,8 +318,9 @@
border-radius: 50%;
opacity: 0;
}
.pulse .wave::after {
content: '';
content: "";
position: absolute;
left: 7%;
top: 7%;

View File

@@ -1,38 +1,40 @@
// ### FONTS
$prim-font: "Roboto", sans-serif;
$sec-font: "Roboto Condensed", sans-serif;
html, body {
// ### BASE
html,
body {
overflow-x: hidden;
}
// ### SCROLLBAR
::-webkit-scrollbar {
width: 15px;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
::-webkit-scrollbar-thumb {
background: -webkit-gradient(linear,left top,left bottom,from(#999),to(#27292F));
background: linear-gradient(to bottom,#999,#27292F);
background: linear-gradient(to bottom, #999, #27292f);
}
// ### TEXT SELECTION
::selection {
background: #FF8901!important; /* WebKit/Blink Browsers */
color: white!important;
text-shadow: 1px 1px 1px rgba(0, 0, 0, .5)!important;
background: #ff8901 !important;
color: white !important;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5) !important;
}
::-moz-selection {
background: #FF8901!important; /* WebKit/Blink Browsers */
color: white!important;
text-shadow: 1px 1px 1px rgba(0, 0, 0, .5)!important;
background: #ff8901 !important;
color: white !important;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5) !important;
}
/*
* HEADER
*/
// ### HEADER
#header {
height: 80px;
background-color: #0c1013;
@@ -40,7 +42,6 @@ html, body {
position: relative;
z-index: 100;
text-align: justify;
display: flex;
justify-content: space-between;
@@ -50,10 +51,10 @@ html, body {
& > a > img {
width: 57px;
display: inline-block;
transition: .5s opacity ease-in;
transition: 0.5s opacity ease-in;
&:hover {
opacity: .8;
opacity: 0.8;
}
}
@@ -75,11 +76,11 @@ html, body {
& > a {
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, .5);
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
text-decoration: none;
&:hover {
color: #8AC832;
color: #8ac832;
}
}
}
@@ -89,7 +90,6 @@ html, body {
font-family: $sec-font;
font-size: 14px;
font-weight: bold;
letter-spacing: 0.1px;
margin-top: 2px;
letter-spacing: -0.01px;
}
@@ -121,7 +121,7 @@ html, body {
font-size: 16px;
text-transform: uppercase;
font-weight: bold;
transition: .2s color;
transition: 0.2s color;
padding: 10px 0;
& > svg {
@@ -133,15 +133,12 @@ html, body {
margin-left: 3px;
}
&:hover, &.active {
color: #8AC832;
&:hover,
&.active {
color: #8ac832;
&:after {
-webkit-transform: scaleX(1);
-ms-transform: scaleX(1);
transform: scaleX(1);
-webkit-transform-origin: left;
-ms-transform-origin: left;
transform-origin: left;
}
}
@@ -151,29 +148,24 @@ html, body {
display: block;
height: 3px;
border-radius: 10px;
-webkit-transform: scaleX(0);
-ms-transform: scaleX(0);
transform: scaleX(0);
transition: .4s -webkit-transform;
transition: .4s transform;
-webkit-transform-origin: 100% 0;
-ms-transform-origin: 100% 0;
transition: 0.4s transform;
transform-origin: 100% 0;
background: #8AC832;
opacity: 0.90;
background: #8ac832;
opacity: 0.9;
position: absolute;
bottom: 5px;
left: 0;
width: 100%;
z-index: 333;
}
}
}
}
}
}
// ### SEARCH BAR
.search {
display: block;
margin: 20px 0;
@@ -189,15 +181,13 @@ html, body {
border: 1px solid rgba(6, 6, 6, 0.99);
background-color: #0f1012;
outline: none;
color: #D1D1D1;
color: #d1d1d1;
font-family: $sec-font;
font-size: 17px;
font-weight: 600;
line-height: 70.58px;
padding-left: 20px;
padding-right: 45px;
padding-right: 45px;
&::placeholder {
color: #676767;
@@ -205,7 +195,7 @@ html, body {
}
&:focus + button + small {
opacity: .3;
opacity: 0.3;
}
}
@@ -220,7 +210,7 @@ html, body {
width: 30px;
height: 30px;
opacity: 1;
transition: .3s opacity;
transition: 0.3s opacity;
&::before {
content: "";
@@ -233,14 +223,12 @@ html, body {
background-repeat: no-repeat;
background-size: 75%;
background-position: center center;
transition: all .3s;
transition: all 0.3s;
}
&:hover {
&::before {
&:hover::before {
cursor: pointer;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAbCAYAAABvCO8sAAABN2lDQ1BBZG9iZSBSR0IgKDE5OTgpAAAokZWPv0rDUBSHvxtFxaFWCOLgcCdRUGzVwYxJW4ogWKtDkq1JQ5ViEm6uf/oQjm4dXNx9AidHwUHxCXwDxamDQ4QMBYvf9J3fORzOAaNi152GUYbzWKt205Gu58vZF2aYAoBOmKV2q3UAECdxxBjf7wiA10277jTG+38yH6ZKAyNguxtlIYgK0L/SqQYxBMygn2oQD4CpTto1EE9AqZf7G1AKcv8ASsr1fBBfgNlzPR+MOcAMcl8BTB1da4Bakg7UWe9Uy6plWdLuJkEkjweZjs4zuR+HiUoT1dFRF8jvA2AxH2w3HblWtay99X/+PRHX82Vun0cIQCw9F1lBeKEuf1UYO5PrYsdwGQ7vYXpUZLs3cLcBC7dFtlqF8hY8Dn8AwMZP/fNTP8gAAAAJcEhZcwAACxMAAAsTAQCanBgAAAXRaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0MiA3OS4xNjA5MjQsIDIwMTcvMDcvMTMtMDE6MDY6MzkgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjMtMDQtMjNUMDQ6MzE6NTgrMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIzLTA0LTIzVDA0OjM0OjI3KzA1OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIzLTA0LTIzVDA0OjM0OjI3KzA1OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowZDA5NTdiMi0zYmM3LTcxNDItODcyNS01ODA3MjA2NTFlYTIiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDoxM2UwZWYxNi03OGM0LTE2NGMtODc1Mi0xYjY5OTQ1OTczMGMiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo5YjNkZTI4Yy1iOTBmLTNjNDUtYjAwNS1kNTExOTE3ZDhkNzIiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjliM2RlMjhjLWI5MGYtM2M0NS1iMDA1LWQ1MTE5MTdkOGQ3MiIgc3RFdnQ6d2hlbj0iMjAyMy0wNC0yM1QwNDozMTo1OCswNTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDowZDA5NTdiMi0zYmM3LTcxNDItODcyNS01ODA3MjA2NTFlYTIiIHN0RXZ0OndoZW49IjIwMjMtMDQtMjNUMDQ6MzQ6MjcrMDU6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4Wh528AAAC7UlEQVRIiaXWX4gXVRTA8c9v2jLJ6A8ERVAJbUakTr2YqRFl0QRRRk89FBGYqNDLUgY51PQiRERJWhhLQY8RtA+OFm2mGf2RGqEQDAvyoaigrKhNk+3h3mGnX7/9zbgeGO45555zvvfCvWdub/POpTrI6vilGMVF6OEnHMEXmCyyaldboV4LcCPWYnGXVeEQdhRZ9fypApdhG65v+D7GPhwWdjYt7HQUK7GiEfslNhRZtbcL8EG81rDH8TI+G7SyIqtAXqYp1uGRxvT6Iqu2N+OTvvyHG7DvcEv0DYT1gasiq9YJO/06urflZbpxNuAyvBr1z7EE77eBBoA/irn7o2trXqar+4Ej2Bn177Ecx04V1oBOYZVwgmEiL9N5TeATuDDqGY7PFdaATuP2aM7HMzXwTDweJ8ZxcEids4VT2RX6DV6K5lhepgsSrME50flUS41VwrUY9/8DN5vUNXu4P8Fd0XEAR1uS58XxIeGSr2yjFVn1MyajeWeCa6MxOTjlP3KioV+FfXmZFh3y9sRxUYLLonFkcGyrbM7LdH9eptcNianv5SUJFkTjtzkC4UZ8MGS+vmLzE/wZjXNPA3gI9w2Zr2tPjQgt7HwsnCNse5FV61tirozjDwm+isbNHYqf0dB/xD0dYHBTHA8nZlraclzckjgdxwkswtttpLxMzzPTcXYleBN/R8eTLfkf4gbcjV/bYI2avai/kWAKz0XHhrjy2eR3fNIRJC/TSzEWza1FVh2r29PT+CPqu7sW7CDvxPGk2K9r4HEzLe5y7MVZp0PKy3Q3ronmvUVW/UX4D9ayB4/iBaFJH8QDOvzt+0BL8LrwwoNNRVZN1PMjffEv4h/hl3I1Po0LeEW43MNAo8ILb6zhfqzIqmebcbO92m6N0OYBelc4pYNebStwRyP2W+HVVvYX7t9hLe8JO9wUV70Qt8VvmBzFDmwpsurEoIDZgLVsid8aYddLcQUuEO7WL0JrrIQH11tFVp0cVvBfVZDA+HDoxOQAAAAASUVORK5CYII=");
}
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAbCAYAAABvCO8sAAABN2lDQ1BBZG9iZSBSR0IgKDE5OTgpAAAokZWPv0rDUBSHvxtFxaFWCOLgcCdRUGzVwYxJW4ogWKtDkq1JQ5ViEm6uf/oQjm4dXNx9AidHwUHxCXwDxamDQ4QMBYvf9J3fORzOAaNi152GUYbzWKt205Gu58vZF2aYAoBOmKV2q3UAECdxxBjf7wiA10277jTG+38yH6ZKAyNguxtlIYgK0L/SqQYxBMygn2oQD4CpTto1EE9AqZf7G1AKcv8ASsr1fBBfgNlzPR+MOcAMcl8BTB1da4Bakg7UWe9Uy6plWdLuJkEkjweZjs4zuR+HiUoT1dFRF8jvA2AxH2w3HblWtay99X/+PRHX82Vun0cIQCw9F1lBeKEuf1UYO5PrYsdwGQ7vYXpUZLs3cLcBC7dFtlqF8hY8Dn8AwMZP/fNTP8gAAAAJcEhZcwAACxMAAAsTAQCanBgAAAXRaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0MiA3OS4xNjA5MjQsIDIwMTcvMDcvMTMtMDE6MDY6MzkgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjMtMDQtMjNUMDQ6MzE6NTgrMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIzLTA0LTIzVDA0OjM0OjI3KzA1OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIzLTA0LTIzVDA0OjM0OjI3KzA1OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowZDA5NTdiMi0zYmM3LTcxNDItODcyNS01ODA3MjA2NTFlYTIiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDoxM2UwZWYxNi03OGM0LTE2NGMtODc1Mi0xYjY5OTQ1OTczMGMiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo5YjNkZTI4Yy1iOTBmLTNjNDUtYjAwNS1kNTExOTE3ZDhkNzIiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjliM2RlMjhjLWI5MGYtM2M0NS1iMDA1LWQ1MTE5MTdkOGQ3MiIgc3RFdnQ6d2hlbj0iMjAyMy0wNC0yM1QwNDozMTo1OCswNTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDowZDA5NTdiMi0zYmM3LTcxNDItODcyNS01ODA3MjA2NTFlYTIiIHN0RXZ0OndoZW49IjIwMjMtMDQtMjNUMDQ6MzQ6MjcrMDU6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4Wh528AAAC7UlEQVRIiaXWX4gXVRTA8c9v2jLJ6A8ERVAJbUakTr2YqRFl0QRRRk89FBGYqNDLUgY51PQiRERJWhhLQY8RtA+OFm2mGf2RGqEQDAvyoaigrKhNk+3h3mGnX7/9zbgeGO45585zvvfCvWdub/POpTrI6vilGMVF6OEnHMEXmCyyaldboV4LcCPWYnGXVeEQdhRZ9fypApdhG65v+D7GPhwWdjYt7HQUK7GiEfslNhRZtbcL8EG81rDH8TI+G7SyIqtAXqYp1uGRxvT6Iqu2N+OTvvyHG7DvcEv0DYT1gasiq9YJO/06urflZbpxNuAyvBr1z7EE77eBBoA/irn7o2trXqar+4Ej2Bn177Ecx04V1oBOYZVwgmEiL9N5TeATuDDqGY7PFdaATuP2aM7HMzXwTDweJ8ZxcEids4VT2RX6DV6K5lhepgsSrME50flUS41VwrUY9/8DN5vUNXu4P8Fd0XEAR1uS58XxIeGSr2yjFVn1MyajeWeCa6MxOTjlP3KioV+FfXmZFh3y9sRxUYLLonFkcGyrbM7LdH9eptcNianv5SUJFkTjtzkC4UZ8MGS+vmLzE/wZjXNPA3gI9w2Zr2tPjQgt7HwsnCNse5FV61tirozjDwm+isbNHYqf0dB/xD0dYHBTHA8nZlraclzckjgdxwkswtttpLxMzzPTcXYleBN/R8eTLfkf4gbcjV/bYI2avai/kWAKz0XHhrjy2eR3fNIRJC/TSzEWza1FVh2r29PT+CPqu7sW7CDvxPGk2K9r4HEzLe5y7MVZp0PKy3Q3ronmvUVW/UX4D9ayB4/iBaFJH8QDOvzt+0BL8LrwwoNNRVZN1PMjffEv4h/hl3I1Po0LeEW43MNAo8ILb6zhfqzIqmebcbO92m6N0OYBelc4pYNebStwRyP2W+HVVvYX7t9hLe8JO9wUV70Qt8VvmBzFDmwpsurEoIDZgLVsid8aYddLcQUuEO7WL0JrrIQH11tFVp0cVvBfVZDA+HDoxOQAAAAASUVORK5CYII=");
}
}
@@ -252,57 +240,55 @@ html, body {
line-height: 1.7em;
top: 8px;
right: 75px;
background-color: #D1D1D1;
color: #080C0F;
background-color: #d1d1d1;
color: #080c0f;
padding: 0 7px;
padding-top: 2px;
border-radius: 4px;
opacity: 0;
transition: opacity .3s;
transition: opacity 0.3s;
cursor: default;
}
}
&.active {
small {
opacity: .3;
opacity: 0.3;
}
}
}
// ### RESPONSIVE
@media (max-width: 1364px) {
#content>.inner>section.materials>header>h1 {
#content > .inner > section.materials > header > h1 {
font-size: 26px;
}
#content>.inner>section.materials>article>.details>h1 {
#content > .inner > section.materials > article > .details > h1 {
font-size: 25px;
}
#content>.inner>section.materials>article>.details>blockquote {
#content > .inner > section.materials > article > .details > blockquote {
font-size: 17px;
line-height: 24px;
-webkit-line-clamp: 4;
}
.btn {
font-size: 16px!important;
font-size: 16px !important;
}
#paginator>header>h1 {
font-size: 30px!important;
#paginator > header > h1 {
font-size: 30px !important;
}
#paginator>.paginator-wrapper>.paginator-box {
#paginator > .paginator-wrapper > .paginator-box {
transform: scale(1.5);
margin-top: 15px;
}
#paginator>.paginator-wrapper>small:first-child {
display: none;
}
#paginator>.paginator-wrapper>small:last-child {
#paginator > .paginator-wrapper > small:first-child,
#paginator > .paginator-wrapper > small:last-child {
display: none;
}
}

View File

@@ -1,31 +1,29 @@
/*
SOME DEFAULT CSS.
*/
html, body {
// ### BASE STYLES
html,
body {
margin: 0;
padding: 0;
min-height: 100vh;
min-width: 100vw;
background-color: #4C5062;
background-color: #4c5062;
color: white;
// assistant page background
&.assist-page {
background: rgb(24,123,123);
background: -moz-radial-gradient(circle, rgba(24,123,123,0.4906337535014006) 0%, rgba(13,15,19,1) 64%);
background: -webkit-radial-gradient(circle, rgba(24,123,123,0.4906337535014006) 0%, rgba(13,15,19,1) 64%);
background: radial-gradient(circle, rgba(24,123,123,0.4906337535014006) 0%, rgba(13,15,19,1) 64%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#187b7b",endColorstr="#0d0f13",GradientType=1);
background: radial-gradient(
circle,
rgba(24, 123, 123, 0.49) 0%,
rgba(13, 15, 19, 1) 64%
);
&.assist-active {
background: rgb(24,81,123);
background: -moz-radial-gradient(circle, rgba(24,81,123,0.6418942577030813) 0%, rgba(13,15,19,1) 64%);
background: -webkit-radial-gradient(circle, rgba(24,81,123,0.6418942577030813) 0%, rgba(13,15,19,1) 64%);
background: radial-gradient(circle, rgba(24,81,123,0.6418942577030813) 0%, rgba(13,15,19,1) 64%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#18517b",endColorstr="#0d0f13",GradientType=1);
background: radial-gradient(
circle,
rgba(24, 81, 123, 0.64) 0%,
rgba(13, 15, 19, 1) 64%
);
}
}
}
@@ -45,9 +43,8 @@ html, body {
-webkit-text-size-adjust: 100%;
}
a {
color: #2A9CD0;
color: #2a9cd0;
text-decoration: none;
font-weight: bold;
@@ -57,29 +54,32 @@ a {
}
}
/*
OVERRIDES.
*/
// ### LAYOUT OVERRIDES
#wrapper {
padding: 0;
margin: 0;
}
#header, main {
#header,
main {
padding: 0 30px;
}
select, input {
color: #ccc!important;
// ### FORM OVERRIDES
select,
input {
color: #ccc !important;
}
.form {
label {
font-weight: bold!important;
color: #8AC832!important;
font-size: 15px!important;
font-weight: bold !important;
color: #8ac832 !important;
font-size: 15px !important;
border-bottom: 1px dotted gray;
margin-bottom: 10px!important;
margin-bottom: 10px !important;
& + div {
line-height: 1.2em;
@@ -87,6 +87,6 @@ select, input {
}
.svelteui-Tab-label {
font-size: 18px!important;
font-size: 18px !important;
}
}

View File

@@ -1,61 +1,27 @@
import { invoke } from "@tauri-apps/api/core"
import { is_listening, isListening } from "@/stores"
import { clearInterval, clearTimeout, setInterval, setTimeout } from 'worker-timers';
// setInterval(() => {
// (async () => {
// is_listening.set(await invoke("is_listening"));
// })().catch(err => {
// console.error(err);
// });
// }, 1000);
// ### UTILITY FUNCTIONS
export function startListening() {
(async () => {
invoke('start_listening')
.then((message) => {
is_listening.set(true);
})
.catch((error) => {
is_listening.set(false);
console.error(error);
// alert("Ошибка: " + error);
})
})().catch(err => {
console.error(err);
});
export function capitalizeFirstLetter(str: string): string {
if (!str) return ""
return str.charAt(0).toUpperCase() + str.slice(1)
}
export function stopListening(callback: () => void) {
(async () => {
invoke('stop_listening')
.then((message) => {
is_listening.set(false);
if(callback) {
callback();
}
})
.catch((error) => {
console.error(error);
})
})().catch(err => {
console.error(err);
});
export function showInExplorer(path: string): void {
invoke("show_in_folder", { path })
.catch(err => console.error("failed to open explorer:", err))
}
export function capitalizeFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
// ### LISTENER FUNCTIONS
// removed since gui now doesn't handle listening
export function startListening(): void {
// disabled in GUI - listening is handled by the tray app
console.log("[gui] listening not available in settings app")
}
export function showInExplorer(path: any) {
(async () => {
invoke('show_in_folder', {path: path})
.then((message) => {})
.catch((error) => {
console.error(error);
// alert("Ошибка: " + error);
})
})().catch(err => {
console.error(err);
});
export function stopListening(callback?: () => void): void {
// disabled in GUI - just call the callback if provided
console.log("[gui] listening not available in settings app")
if (callback) callback()
}

View File

@@ -1,13 +1,12 @@
// Klondike project old CSS file
import "./css/main.scss";
// ### STYLES
import "./css/main.scss"
import "./css/styles.scss"
// App current CSS file
import "./css/styles.scss";
// ### APP
import App from "./App.svelte"
// deploy app
import App from "./App.svelte";
const app = new App({
target: document.getElementById("app")!,
});
target: document.getElementById("app")!
})
export default app;
export default app

View File

@@ -1,7 +1,6 @@
<!-- _layout.svelte -->
<script>
import { Container } from '@svelteuidev/core'
import Header from '@/components/Header.svelte'
<script lang="ts">
import { Container } from "@svelteuidev/core"
import Header from "@/components/Header.svelte"
</script>
<Container fluid id="wrapper">

View File

@@ -1,23 +1,39 @@
<script lang="ts">
import { Notification, Space } from "@svelteuidev/core"
import { InfoCircled } from "radix-icons-svelte"
import HDivider from "@/components/elements/HDivider.svelte"
import Footer from "@/components/Footer.svelte"
import { appInfo } from "@/stores"
import { Notification, Space } from '@svelteuidev/core';
import { InfoCircled } from 'radix-icons-svelte';
import { tg_official_link } from "@/stores";
let tgLink = ""
appInfo.subscribe(info => {
tgLink = info.tgOfficialLink
})
</script>
<Space h="xl" />
<Notification title='[404] Этот раздел еще находится в разработке!' icon={InfoCircled} color='blue' withCloseButton={false}>
Тут будет список команд + полноценный редактор команд.<br>
Следите за обновлениями в <a href="{tg_official_link}" target="_blank">нашем телеграм канале</a>!
<Notification
title="[404] Этот раздел еще находится в разработке!"
icon={InfoCircled}
color="blue"
withCloseButton={false}
>
Тут будет список команд + полноценный редактор команд.<br />
Следите за обновлениями в <a href={tgLink} target="_blank">нашем телеграм канале</a>!
</Notification>
<div style="text-align: center;margin-top: 25px;">
<img src="/media/images/tenor.gif" alt="bruh" width="320px">
<div class="placeholder-image">
<img src="/media/images/tenor.gif" alt="bruh" width="320px" />
</div>
<HDivider />
<Footer />
<style>
.placeholder-image {
text-align: center;
margin-top: 25px;
}
</style>

View File

@@ -1,71 +1,46 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte"
import { Notification, Space } from "@svelteuidev/core"
import { InfoCircled } from "radix-icons-svelte"
import SearchBar from "@/components/elements/SearchBar.svelte"
import ArcReactor from "@/components/elements/ArcReactor.svelte"
import HDivider from "@/components/elements/HDivider.svelte"
import Stats from "@/components/elements/Stats.svelte"
import Footer from "@/components/Footer.svelte"
import { Notification, Space } from '@svelteuidev/core'
import { InfoCircled } from 'radix-icons-svelte'
import { onMount, onDestroy } from 'svelte'
import { isListening } from "@/stores"
onMount(async () => {
document.body.classList.add('assist-page');
});
let listening = false
isListening.subscribe(value => {
listening = value
})
onDestroy(async () => {
document.body.classList.remove('assist-page');
});
onMount(() => {
document.body.classList.add("assist-page")
})
import { is_listening } from "@/stores"
let is_listening__val: boolean;
is_listening.subscribe(value => {
is_listening__val = value;
});
onDestroy(() => {
document.body.classList.remove("assist-page")
})
</script>
<HDivider />
{#if !is_listening__val}
<Notification title='Внимание!' icon={InfoCircled} color='cyan' withCloseButton={false}>
В данный момент ассистент не прослушивает команды.<br />
Пожалуйста, <a href="/settings">перейдите в настройки</a> и введите ключ Picovoice.
</Notification>
<!-- <SearchBar /> -->
{#if !listening}
<Notification
title="Внимание!"
icon={InfoCircled}
color="cyan"
withCloseButton={false}
>
В данный момент ассистент не прослушивает команды.<br />
Пожалуйста, <a href="/settings">перейдите в настройки</a> и введите ключ Picovoice.
</Notification>
{:else}
<!-- <SearchBar /> -->
<ArcReactor />
<ArcReactor />
{/if}
<HDivider no_margin />
<HDivider noMargin />
<Stats />
<Footer />
<!--
<Title order={1}>This is h1 title</Title>
<Title order={1} variant='gradient' gradient={{from: 'blue', to: 'red', deg: 45}}>This is h1 title with a twist</Title>
<Menu>
<Button slot="control" variant="gradient" gradient={{ from: 'blue', to: 'teal', deg: 50 }} radius="md" size="md">Toggle Menu</Button>
<Menu.Label>Application</Menu.Label>
<Menu.Item icon={Gear}>Settings</Menu.Item>
<Menu.Item icon={ChatBubble}>Messages</Menu.Item>
<Menu.Item icon={Camera}>Gallery</Menu.Item>
<Menu.Item icon={MagnifyingGlass}>
<svelte:fragment slot='rightSection'>
<Text size="xs" color="dimmed">⌘K</Text>
</svelte:fragment>
Search
</Menu.Item>
<Divider />
<Menu.Label>Danger zone</Menu.Label>
<Menu.Item icon={Width}>Transfer my data</Menu.Item>
<Menu.Item color="red" icon={Trash}>Delete my account</Menu.Item>
</Menu>
<Checkbox bind:checked={checked} label="I agree to sell my privacy" />
{checked}
{#if checked}
YEP!
{/if} -->

View File

@@ -1,186 +1,278 @@
<script lang="ts">
// IMPORTS
import { onMount } from "svelte"
import { invoke } from "@tauri-apps/api/core"
import { goto } from '@roxi/routify'
import { onMount } from 'svelte'
import { startListening, stopListening, showInExplorer } from "@/functions";
// import { setTimeout } from 'worker-timers';
import { goto } from "@roxi/routify"
import { setTimeout } from "worker-timers"
import { feedback_link, log_file_path } from "@/stores";
import { showInExplorer, stopListening, startListening } from "@/functions"
import { appInfo, assistantVoice } from "@/stores"
// COMPONENTS & STUFF
import HDivider from "@/components/elements/HDivider.svelte"
import Footer from "@/components/Footer.svelte"
import { Notification, Button, Text, Tabs, Space, Alert, Input, InputWrapper, NativeSelect } from '@svelteuidev/core';
import { Check, Mix, Cube, Code, Gear, QuestionMarkCircled, CrossCircled } from 'radix-icons-svelte';
import {
Notification,
Button,
Text,
Tabs,
Space,
Alert,
Input,
InputWrapper,
NativeSelect
} from "@svelteuidev/core"
// VARIABLES
import {
Check,
Mix,
Cube,
Code,
Gear,
QuestionMarkCircled,
CrossCircled
} from "radix-icons-svelte"
let available_microphones: { label: string; value: number }[] = [];
let settings_saved = false;
let save_button_disabled = false;
let assistant_voice_val = ""; // shared
let selected_microphone = "";
let selected_wake_word_engine = "";
let api_key__picovoice = "";
let api_key__openai = "";
// SHARED VALUES
import { assistant_voice } from "@/stores"
assistant_voice.subscribe(value => {
assistant_voice_val = value;
});
// FUNCTIONS
async function save_settings() {
save_button_disabled = true; // disable save button for a while
settings_saved = false; // hide alert
await invoke("db_write", {key: "assistant_voice", val: assistant_voice_val});
await invoke("db_write", {key: "selected_microphone", val: selected_microphone});
await invoke("db_write", {key: "selected_wake_word_engine", val: selected_wake_word_engine});
await invoke("db_write", {key: "api_key__picovoice", val: api_key__picovoice});
await invoke("db_write", {key: "api_key__openai", val: api_key__openai});
// update shared
assistant_voice.set(assistant_voice_val);
settings_saved = true; // show alert
setTimeout(() => {
settings_saved = false; // hide alert again after N seconds
}, 5000);
setTimeout(() => {
save_button_disabled = false; // enable save button again
}, 1000);
// restart listening everytime new settings is saved
stopListening(() => {
startListening();
});
// ### STATE
interface MicrophoneOption {
label: string
value: string
}
// CODE
let availableMicrophones: MicrophoneOption[] = []
let settingsSaved = false
let saveButtonDisabled = false
// form values
let voiceVal = ""
let selectedMicrophone = ""
let selectedWakeWordEngine = ""
let apiKeyPicovoice = ""
let apiKeyOpenai = ""
// subscribe to stores
assistantVoice.subscribe(value => {
voiceVal = value
})
let feedbackLink = ""
let logFilePath = ""
appInfo.subscribe(info => {
feedbackLink = info.feedbackLink
logFilePath = info.logFilePath
})
// ### FUNCTIONS
async function saveSettings() {
saveButtonDisabled = true
settingsSaved = false
try {
await Promise.all([
invoke("db_write", { key: "assistant_voice", val: voiceVal }),
invoke("db_write", { key: "selected_microphone", val: selectedMicrophone }),
invoke("db_write", { key: "selected_wake_word_engine", val: selectedWakeWordEngine }),
invoke("db_write", { key: "api_key__picovoice", val: apiKeyPicovoice }),
invoke("db_write", { key: "api_key__openai", val: apiKeyOpenai })
])
// update shared store
assistantVoice.set(voiceVal)
settingsSaved = true
// hide alert after 5 seconds
setTimeout(() => {
settingsSaved = false
}, 5000)
// restart listening with new settings
stopListening(() => startListening())
} catch (err) {
console.error("failed to save settings:", err)
}
setTimeout(() => {
saveButtonDisabled = false
}, 1000)
}
// ### INIT
onMount(async () => {
// preload some vars
let _available_microphones: Array<Number> = await invoke("pv_get_audio_devices");
Object.entries(_available_microphones).forEach(entry => {
const [k, v] = entry;
try {
// load microphones
const mics = await invoke<string[]>("pv_get_audio_devices")
availableMicrophones = mics.map((name, idx) => ({
label: name,
value: String(idx)
}))
available_microphones.push({
label: String(v),
value: Number(k)
});
});
available_microphones = available_microphones; // update component options
// load values from db
// assistant_voice.set(await invoke("db_read", {key: "assistant_voice"}));
selected_microphone = await invoke("db_read", {key: "selected_microphone"});
selected_wake_word_engine = await invoke("db_read", {key: "selected_wake_word_engine"});
api_key__picovoice = await invoke("db_read", {key: "api_key__picovoice"});
api_key__openai = await invoke("db_read", {key: "api_key__openai"});
});
// load settings from db
const [mic, wakeWord, pico, openai] = await Promise.all([
invoke<string>("db_read", { key: "selected_microphone" }),
invoke<string>("db_read", { key: "selected_wake_word_engine" }),
invoke<string>("db_read", { key: "api_key__picovoice" }),
invoke<string>("db_read", { key: "api_key__openai" })
])
selectedMicrophone = mic
selectedWakeWordEngine = wakeWord
apiKeyPicovoice = pico
apiKeyOpenai = openai
} catch (err) {
console.error("failed to load settings:", err)
}
})
</script>
<Space h="xl" />
<Notification title='БЕТА версия!' icon={QuestionMarkCircled} color='blue' withCloseButton={false}>
<Notification
title="БЕТА версия!"
icon={QuestionMarkCircled}
color="blue"
withCloseButton={false}
>
Часть функций может работать некорректно.<br />
Сообщайте обо всех найденных багах в <a href="{feedback_link}" target="_blank">наш телеграм бот</a>.
Сообщайте обо всех найденных багах в <a href={feedbackLink} target="_blank">наш телеграм бот</a>.
<Space h="sm" />
<Button color="gray" radius="md" size="xs" uppercase on:click={() => {showInExplorer(log_file_path)}}>Открыть папку с логами</Button>
<Button
color="gray"
radius="md"
size="xs"
uppercase
on:click={() => showInExplorer(logFilePath)}
>
Открыть папку с логами
</Button>
</Notification>
<Space h="xl" />
{#if settings_saved }
<Notification title='Настройки сохранены!' icon={Check} color='teal' on:close="{() => {settings_saved = false}}"></Notification>
<Space h="xl" />
{#if settingsSaved}
<Notification
title="Настройки сохранены!"
icon={Check}
color="teal"
on:close={() => { settingsSaved = false }}
/>
<Space h="xl" />
{/if}
<Tabs class="form" color='#8AC832' position="left">
<Tabs.Tab label='Общее' icon={Gear}>
<Tabs class="form" color="#8AC832" position="left">
<!-- general tab -->
<Tabs.Tab label="Общее" icon={Gear}>
<Space h="sm" />
<NativeSelect data={[
{ label: 'Jarvis ремейк (от Хауди)', value: 'jarvis-remake' },
{ label: 'Jarvis OG (из фильмов)', value: 'jarvis-og' }
<NativeSelect
data={[
{ label: "Jarvis ремейк (от Хауди)", value: "jarvis-remake" },
{ label: "Jarvis OG (из фильмов)", value: "jarvis-og" }
]}
label="Голос ассистента"
description="Не все команды работают со всеми звуковыми пакетами."
variant="filled"
bind:value={assistant_voice_val}
bind:value={voiceVal}
/>
</Tabs.Tab>
<Tabs.Tab label='Устройства' icon={Mix}>
<Space h="sm" />
<NativeSelect data={available_microphones}
<!-- devices tab -->
<Tabs.Tab label="Устройства" icon={Mix}>
<Space h="sm" />
<NativeSelect
data={availableMicrophones}
label="Выберите микрофон"
description="Его будет слушать ассистент."
variant="filled"
bind:value={selected_microphone}
bind:value={selectedMicrophone}
/>
</Tabs.Tab>
<Tabs.Tab label='Нейросети' icon={Cube}>
<Space h="sm" />
<NativeSelect data={[
{ label: 'Rustpotter', value: 'Rustpotter' },
{ label: 'Vosk (медленный)', value: 'Vosk' },
{ label: 'Picovoice Porcupine (требует API ключ)', value: 'Picovoice' }
<!-- neural networks tab -->
<Tabs.Tab label="Нейросети" icon={Cube}>
<Space h="sm" />
<NativeSelect
data={[
{ label: "Rustpotter", value: "Rustpotter" },
{ label: "Vosk (медленный)", value: "Vosk" },
{ label: "Picovoice Porcupine (требует API ключ)", value: "Picovoice" }
]}
label="Распознавание активационной фразы (Wake Word)"
description="Выберите, какая нейросеть будет отвечать за распознавание активационной фразы."
variant="filled"
bind:value={selected_wake_word_engine}
bind:value={selectedWakeWordEngine}
/>
{#if selected_wake_word_engine == "picovoice"}
{#if selectedWakeWordEngine === "picovoice"}
<Space h="sm" />
<Alert title="Внимание!" color="#868E96" variant="outline">
<Notification title='Эта нейросеть работает не у всех!' icon={CrossCircled} color='orange' withCloseButton={false}>
<Notification
title="Эта нейросеть работает не у всех!"
icon={CrossCircled}
color="orange"
withCloseButton={false}
>
Мы ждем официального патча от разработчиков.
</Notification>
<Space h="sm" />
<Text size='sm' color="gray">
<Text size="sm" color="gray">
Введите сюда свой ключ Picovoice.<br />
Он выдается бесплатно при регистрации в <a href='https://console.picovoice.ai/' target="_blank">Picovoice Console</a>.<br>
Он выдается бесплатно при регистрации в
<a href="https://console.picovoice.ai/" target="_blank">Picovoice Console</a>.
</Text>
<Space h="sm" />
<Input icon={Code} placeholder='Ключ Picovoice' variant='filled' autocomplete="off" bind:value={api_key__picovoice}/>
<Input
icon={Code}
placeholder="Ключ Picovoice"
variant="filled"
autocomplete="off"
bind:value={apiKeyPicovoice}
/>
</Alert>
{/if}
<Space h="xl" />
<InputWrapper label="Ключ OpenAI">
<!-- <Text size='sm' color="gray">Введите сюда свой ключ OpenAI, он требуется для работы ChatGPT.<br />Получить его можно <a href="https://chat.openai.com/auth/login" target="_blank">на официальном сайте OpenAI</a>.</Text> -->
<Text size='sm' color="gray">В данный момент ChatGPT <u>не поддерживается</u>. Он будет добавлен в ближайших обновлениях.</Text>
<Text size="sm" color="gray">
В данный момент ChatGPT <u>не поддерживается</u>.
Он будет добавлен в ближайших обновлениях.
</Text>
<Space h="sm" />
<Input icon={Code} placeholder='Ключ OpenAI' variant='filled' autocomplete="off" bind:value={api_key__openai} disabled/>
<Input
icon={Code}
placeholder="Ключ OpenAI"
variant="filled"
autocomplete="off"
bind:value={apiKeyOpenai}
disabled
/>
</InputWrapper>
</Tabs.Tab>
</Tabs>
<Space h="xl" />
<Button color="lime" radius="md" size="sm" uppercase ripple fullSize on:click={save_settings} disabled={save_button_disabled}>
<Button
color="lime"
radius="md"
size="sm"
uppercase
ripple
fullSize
on:click={saveSettings}
disabled={saveButtonDisabled}
>
Сохранить
</Button>
<Space h="sm" />
<Button color="gray" radius="md" size="sm" uppercase fullSize on:click={() => {$goto('/')}}>
<Button
color="gray"
radius="md"
size="sm"
uppercase
fullSize
on:click={() => $goto("/")}
>
Назад
</Button>

View File

@@ -1,34 +1,50 @@
import { writable, get } from "svelte/store"
import { invoke } from "@tauri-apps/api/core"
import { writable } from 'svelte/store'
// listen state
export const is_listening = writable(true);
let is_listening__val: boolean;
is_listening.subscribe(value => {
is_listening__val = value;
});
export function isListening() {return is_listening__val}
// ### LISTENING STATE
// note: defaults to false since GUI doesn't have listening capability
export const isListening = writable(false)
// assistant voice
export const assistant_voice = writable("");
// ### ASSISTANT VOICE
export const assistantVoice = writable("")
(async () => {
assistant_voice.set(await invoke("db_read", {key: "assistant_voice"}));
})().catch(err => {
console.error(err);
});
// load voice setting from db
async function loadVoiceSetting() {
try {
const voice = await invoke<string>("db_read", { key: "assistant_voice" })
assistantVoice.set(voice)
} catch (err) {
console.error("failed to load voice setting:", err)
}
}
loadVoiceSetting()
// etc
export let tg_official_link = "";
export let feedback_link = "";
export let github_repository_link = "";
export let log_file_path = "";
// ### APP INFO
// these are loaded once on startup
export const appInfo = writable({
tgOfficialLink: "",
feedbackLink: "",
repositoryLink: "",
logFilePath: ""
})
(async () => {
tg_official_link = await invoke("get_tg_official_link")
feedback_link = await invoke("get_feedback_link")
github_repository_link = await invoke("get_repository_link")
log_file_path = await invoke("get_log_file_path")
})().catch(err => {
console.error(err);
});
async function loadAppInfo() {
try {
const [tg, feedback, repo, logPath] = await Promise.all([
invoke<string>("get_tg_official_link"),
invoke<string>("get_feedback_link"),
invoke<string>("get_repository_link"),
invoke<string>("get_log_file_path")
])
appInfo.set({
tgOfficialLink: tg,
feedbackLink: feedback,
repositoryLink: repo,
logFilePath: logPath
})
} catch (err) {
console.error("failed to load app info:", err)
}
}
loadAppInfo()

View File

@@ -0,0 +1,40 @@
// vite.config.ts
import { defineConfig } from "file:///D:/Rust/jarvis-app/frontend/node_modules/vite/dist/node/index.js";
import { svelte } from "file:///D:/Rust/jarvis-app/frontend/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
import sveltePreprocess from "file:///D:/Rust/jarvis-app/frontend/node_modules/svelte-preprocess/dist/index.js";
import tsconfigPaths from "file:///D:/Rust/jarvis-app/frontend/node_modules/vite-tsconfig-paths/dist/index.mjs";
import routify from "file:///D:/Rust/jarvis-app/frontend/node_modules/@roxi/routify/lib/extra/vite-plugin/vite-plugin.js";
var vite_config_default = defineConfig({
plugins: [
svelte({
preprocess: [
sveltePreprocess({
typescript: true
})
],
onwarn: (warning, handler) => {
const { code, frame } = warning;
if (code === "css-unused-selector")
return;
handler(warning);
}
}),
routify(),
tsconfigPaths()
],
clearScreen: false,
server: {
port: 1420,
strictPort: true
},
envPrefix: ["VITE_", "TAURI_"],
build: {
target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
sourcemap: !!process.env.TAURI_DEBUG
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxSdXN0XFxcXGphcnZpcy1hcHBcXFxcZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkQ6XFxcXFJ1c3RcXFxcamFydmlzLWFwcFxcXFxmcm9udGVuZFxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRDovUnVzdC9qYXJ2aXMtYXBwL2Zyb250ZW5kL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVcIjtcclxuaW1wb3J0IHsgc3ZlbHRlIH0gZnJvbSBcIkBzdmVsdGVqcy92aXRlLXBsdWdpbi1zdmVsdGVcIjtcclxuaW1wb3J0IHN2ZWx0ZVByZXByb2Nlc3MgZnJvbSBcInN2ZWx0ZS1wcmVwcm9jZXNzXCI7XHJcbmltcG9ydCB0c2NvbmZpZ1BhdGhzIGZyb20gJ3ZpdGUtdHNjb25maWctcGF0aHMnXHJcbmltcG9ydCByb3V0aWZ5IGZyb20gJ0Byb3hpL3JvdXRpZnkvdml0ZS1wbHVnaW4nXHJcblxyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xyXG4gIHBsdWdpbnM6IFtcclxuICAgIHN2ZWx0ZSh7XHJcbiAgICAgIHByZXByb2Nlc3M6IFtcclxuICAgICAgICBzdmVsdGVQcmVwcm9jZXNzKHtcclxuICAgICAgICAgIHR5cGVzY3JpcHQ6IHRydWUsXHJcbiAgICAgICAgfSksXHJcbiAgICAgIF0sXHJcbiAgICAgIG9ud2FybjogKHdhcm5pbmcsIGhhbmRsZXIpID0+IHtcclxuICAgICAgICBjb25zdCB7IGNvZGUsIGZyYW1lIH0gPSB3YXJuaW5nO1xyXG4gICAgICAgIGlmIChjb2RlID09PSBcImNzcy11bnVzZWQtc2VsZWN0b3JcIilcclxuICAgICAgICAgICAgcmV0dXJuO1xyXG5cclxuICAgICAgICBoYW5kbGVyKHdhcm5pbmcpO1xyXG4gICAgICB9LFxyXG4gICAgfSksXHJcbiAgICByb3V0aWZ5KCksXHJcbiAgICB0c2NvbmZpZ1BhdGhzKClcclxuICBdLFxyXG5cclxuICBjbGVhclNjcmVlbjogZmFsc2UsXHJcbiAgc2VydmVyOiB7XHJcbiAgICBwb3J0OiAxNDIwLFxyXG4gICAgc3RyaWN0UG9ydDogdHJ1ZSxcclxuICB9LFxyXG4gIGVudlByZWZpeDogW1wiVklURV9cIiwgXCJUQVVSSV9cIl0sXHJcbiAgYnVpbGQ6IHtcclxuICAgIHRhcmdldDogcHJvY2Vzcy5lbnYuVEFVUklfUExBVEZPUk0gPT0gXCJ3aW5kb3dzXCIgPyBcImNocm9tZTEwNVwiIDogXCJzYWZhcmkxM1wiLFxyXG4gICAgbWluaWZ5OiAhcHJvY2Vzcy5lbnYuVEFVUklfREVCVUcgPyBcImVzYnVpbGRcIiA6IGZhbHNlLFxyXG4gICAgc291cmNlbWFwOiAhIXByb2Nlc3MuZW52LlRBVVJJX0RFQlVHLFxyXG4gIH0sXHJcbn0pOyJdLAogICJtYXBwaW5ncyI6ICI7QUFBMlEsU0FBUyxvQkFBb0I7QUFDeFMsU0FBUyxjQUFjO0FBQ3ZCLE9BQU8sc0JBQXNCO0FBQzdCLE9BQU8sbUJBQW1CO0FBQzFCLE9BQU8sYUFBYTtBQUVwQixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxZQUFZO0FBQUEsUUFDVixpQkFBaUI7QUFBQSxVQUNmLFlBQVk7QUFBQSxRQUNkLENBQUM7QUFBQSxNQUNIO0FBQUEsTUFDQSxRQUFRLENBQUMsU0FBUyxZQUFZO0FBQzVCLGNBQU0sRUFBRSxNQUFNLE1BQU0sSUFBSTtBQUN4QixZQUFJLFNBQVM7QUFDVDtBQUVKLGdCQUFRLE9BQU87QUFBQSxNQUNqQjtBQUFBLElBQ0YsQ0FBQztBQUFBLElBQ0QsUUFBUTtBQUFBLElBQ1IsY0FBYztBQUFBLEVBQ2hCO0FBQUEsRUFFQSxhQUFhO0FBQUEsRUFDYixRQUFRO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixZQUFZO0FBQUEsRUFDZDtBQUFBLEVBQ0EsV0FBVyxDQUFDLFNBQVMsUUFBUTtBQUFBLEVBQzdCLE9BQU87QUFBQSxJQUNMLFFBQVEsUUFBUSxJQUFJLGtCQUFrQixZQUFZLGNBQWM7QUFBQSxJQUNoRSxRQUFRLENBQUMsUUFBUSxJQUFJLGNBQWMsWUFBWTtBQUFBLElBQy9DLFdBQVcsQ0FBQyxDQUFDLFFBQVEsSUFBSTtBQUFBLEVBQzNCO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K