7
404.md
@ -4,8 +4,8 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://eugene-serb.github.io/img/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="https://eugene-serb.github.io/css/styles.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://eugene-serb.github.io/wavelovers/img/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="https://eugene-serb.github.io/wavelovers/css/styles.css" />
|
||||
|
||||
<style type="text/css">
|
||||
div.markdown-body > h1, div.footer {
|
||||
@ -60,11 +60,10 @@
|
||||
<section class="banner-container">
|
||||
<div class="banner">
|
||||
<h1>404</h1>
|
||||
<span>File not found. Please, go to the </span><a href="https://eugene-serb.github.io/" target="_self">homepage</a><br />
|
||||
<span>File not found. Please, go to the </span><a href="https://eugene-serb.github.io/wavelovers/" target="_self">homepage</a><br />
|
||||
<span>Contact info: </span><a href="https://twitter.com/eugene_serb/" target="_blank">@eugene_serb</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="https://eugene-serb.github.io/js/scripts.js"></script>
|
||||
<noscript><div><img src="https://mc.yandex.ru/watch/76610724" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
||||
</body>
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Vibration Master
|
||||
Vibration Master in Javascript, HTML and CSS.
|
||||
# Wavelovers
|
||||
Wavelovers in ***Javascript***, ***HTML*** and ***CSS*** **[[rep](https://github.com/eugene-serb/wavelovers/), [site](https://eugene-serb.github.io/wavelovers/)]**.
|
||||
|
||||
This is a browser vibration master that can make a vibration massager for your arms from your gamepad.
|
||||
This is a Wavelovers that can make a vibration massager from your gamepad.
|
||||
|
||||
If you are interested in this or my other projects, or would like to suggest and share ideas with me, or just talk to me, contact me: *[@eugene_serb](https://t.me/eugene_serb)*
|
||||
|
||||
|
518
css/styles.css
Normal file
@ -0,0 +1,518 @@
|
||||
/* ------------------------------ */
|
||||
/* RESET AND BASE STYLES' TUNE UP */
|
||||
/* ------------------------------ */
|
||||
|
||||
@charset 'UTF-8';
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap');
|
||||
|
||||
*, ::after, ::before {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Simple colors */
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
--color-milk: #F5F5F5;
|
||||
--color-coal: #3C3C3C;
|
||||
/* ---------------------- */
|
||||
/* Palette of five colors */
|
||||
--color-a: #E27396;
|
||||
--color-b: #EA9AB2;
|
||||
--color-c: #EFCFE3;
|
||||
--color-d: #EAF2D7;
|
||||
--color-e: #B3DEE2;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-background: var(--color-white);
|
||||
--color-content-background: var(--color-milk);
|
||||
--color-logo: var(--color-white);
|
||||
--color-header: var(--color-coal);
|
||||
--color-text: var(--color-coal);
|
||||
--color-link: var(--color-b);
|
||||
--color-link-hover: var(--color-a);
|
||||
--color-border: var(--color-a);
|
||||
--color-selection: var(--color-b);
|
||||
--color-table-header-background: transparent;
|
||||
--color-table-header-text: transparent;
|
||||
--color-table-item: transparent;
|
||||
--color-anotation: var(--color-coal);
|
||||
--color-pattern-button: var(--color-c);
|
||||
--color-pattern-text: var(--color-white);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--color-coal);
|
||||
--color-content-background: var(--color-c);
|
||||
--color-logo: var(--color-white);
|
||||
--color-header: var(--color-coal);
|
||||
--color-text: var(--color-coal);
|
||||
--color-link: var(--color-b);
|
||||
--color-link-hover: var(--color-a);
|
||||
--color-border: var(--color-a);
|
||||
--color-selection: var(--color-b);
|
||||
--color-table-header-background: transparent;
|
||||
--color-table-header-text: transparent;
|
||||
--color-table-item: transparent;
|
||||
--color-anotation: var(--color-milk);
|
||||
--color-pattern-button: var(--color-milk);
|
||||
--color-pattern-text: var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
direction: ltr;
|
||||
font-size: 16px;
|
||||
line-height: 1.382em;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 100vh;
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-selection);
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-selection);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: var(--color-header);
|
||||
font-weight: 500;
|
||||
margin-block-start: 0.382em;
|
||||
margin-block-end: 0.618em;
|
||||
line-height: 1.382em;
|
||||
}
|
||||
|
||||
h1 { font-size: 2em; }
|
||||
h2 { font-size: 1.5em; }
|
||||
h3 { font-size: 1.17em; }
|
||||
h4 { font-size: 1em; }
|
||||
h5 { font-size: 0.83em; }
|
||||
h6 { font-size: 0.67em; }
|
||||
big { font-size: larger; }
|
||||
small { font-size: smaller; }
|
||||
|
||||
span, p, article, blockquote {
|
||||
color: var(--color-text);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 16px;
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-weight: 500;
|
||||
color: var(--color-link);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
border-bottom: 2px solid var(--color-link-hover);
|
||||
color: var(--color-link-hover);
|
||||
transition: all 0.5s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ----- */
|
||||
/* FORMS */
|
||||
/* ----- */
|
||||
|
||||
hr {
|
||||
margin-block-start: 0.5em;
|
||||
margin-block-end: 0.5em;
|
||||
}
|
||||
|
||||
button, input, textarea, select {
|
||||
padding: 4px 8px;
|
||||
border: 2px solid var(--color-link);
|
||||
background: var(--color-milk);
|
||||
color: var(--color-text);
|
||||
font-size: 16px;
|
||||
line-height: 1.382em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button:hover, input:hover,
|
||||
textarea:hover, select:hover {
|
||||
border: 2px solid var(--color-link-hover);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
option {
|
||||
background: var(--color-background);
|
||||
color: var(--color-text);
|
||||
font-size: 16px;
|
||||
line-height: 1.382em;
|
||||
}
|
||||
|
||||
option:hover {
|
||||
background-color: var(--color-selection);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 2px solid var(--color-border-alpha);
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 2px 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* ------------- */
|
||||
/* SERVICE RULES */
|
||||
/* ------------- */
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
clip-path: inset(100%);
|
||||
clip: rect(0 0 0 0);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
/* ------ */
|
||||
/* BANNER */
|
||||
/* ------ */
|
||||
|
||||
.banner-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.banner {
|
||||
margin: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* ------------ */
|
||||
/* ITEMS STYLES */
|
||||
/* ------------ */
|
||||
|
||||
/* ------ */
|
||||
/* HEADER */
|
||||
/* ------ */
|
||||
|
||||
.header {
|
||||
border-bottom: 32px solid var(--color-border);
|
||||
background: var(--color-b);
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
padding-top: 64px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo-wrapper__logo {
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
color: var(--color-logo);
|
||||
}
|
||||
|
||||
.logo-wrapper__logo::selection {
|
||||
background-color: var(--color-a);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 540px) {
|
||||
.logo-wrapper__logo {
|
||||
font-size: 64px;
|
||||
font-weight: 500;
|
||||
color: var(--color-logo);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-wrapper {
|
||||
margin-top: 32px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navigation__item {
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.navigation__item a {
|
||||
color: var(--color-logo);
|
||||
}
|
||||
|
||||
.navigation__item a:hover {
|
||||
color: var(--color-logo);
|
||||
border-color: var(--color-logo);
|
||||
}
|
||||
|
||||
.navigation__item a::selection {
|
||||
background: var(--color-a);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 720px) {
|
||||
.header-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 1 / 2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu-wrapper {
|
||||
margin-top: 0px;
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 1 / 2;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------- */
|
||||
/* MAIN, PAGE FLOW AND CONTENT SECTION */
|
||||
/* ----------------------------------- */
|
||||
|
||||
.page {
|
||||
flex-grow: 1;
|
||||
margin-top: 64px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-bottom: 16px;
|
||||
padding: 32px;
|
||||
border-radius: 8px;
|
||||
background: var(--color-content-background);
|
||||
}
|
||||
|
||||
.content__header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ------ */
|
||||
/* FOOTER */
|
||||
/* ------ */
|
||||
|
||||
.footer {
|
||||
border-top: 4px solid var(--color-a);
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
padding-top: 32px;
|
||||
padding-bottom: 32px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.annotation__text {
|
||||
color: var(--color-anotation);
|
||||
}
|
||||
|
||||
.created-by {
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
.created-by > span {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.created-by > a {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.created-by > a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* ----------- */
|
||||
/* WAVE MASTER */
|
||||
/* ----------- */
|
||||
|
||||
.wavelovers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.message {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.pattern-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.pattern-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
width: 300px;
|
||||
height: 64px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--color-pattern-button);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.pattern-item__selected {
|
||||
background: var(--color-b);
|
||||
}
|
||||
|
||||
.pattern-item__icon{
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.pattern-item__name {
|
||||
font-size: 18px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
color: var(--color-pattern-text);
|
||||
}
|
||||
|
||||
.device-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.device-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
max-width: 100%;
|
||||
padding: 16px;
|
||||
border: 4px solid var(--color-b);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.list-item__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.list-item_selected .list-item__info span {
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.controls-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.controls-box__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.controls-box__item > span {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.gamepad-button-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gamepad-button {
|
||||
width: 4ch;
|
||||
height: 4ch;
|
||||
border: 1px solid black;
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: grey;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gamepad-button > span { color: var(--color-white); }
|
||||
|
||||
.gamepad-button_a { background: green; }
|
||||
.gamepad-button_b { background: red; }
|
||||
.gamepad-button_x { background: blue; }
|
||||
.gamepad-button_y { background: orange; }
|
||||
|
@ -1,77 +0,0 @@
|
||||
/* -------------- */
|
||||
/* GAMEPAD MASTER */
|
||||
/* -------------- */
|
||||
|
||||
@charset 'UTF-8';
|
||||
|
||||
.vibration-master {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.message-box {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.device-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.device-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
max-width: 100%;
|
||||
padding: 16px;
|
||||
border: 4px solid var(--color-background);
|
||||
outline: 2px solid var(--color-border-alpha);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 640px) {
|
||||
.list-item {
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item_selected {
|
||||
outline: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
.list-item__info {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.list-item__info > div {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.controls-banner {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controls-banner__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
BIN
img/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
img/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
img/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
img/favicon-16x16.png
Normal file
After Width: | Height: | Size: 602 B |
BIN
img/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
img/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
img/og.png
Normal file
After Width: | Height: | Size: 18 KiB |
83
index.html
@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-us" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Eugene Serb – Vibration Master</title>
|
||||
<title>Wavelovers</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
@ -9,32 +9,35 @@
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="robots" content="all" />
|
||||
|
||||
<link rel="canonical" href="https://eugene-serb.github.io/vibration-master/" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://eugene-serb.github.io/img/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="https://eugene-serb.github.io/css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="css/vibration-master.css" />
|
||||
<link rel="canonical" href="https://eugene-serb.github.io/wavelovers/" />
|
||||
<link rel="stylesheet" type="text/css" href="css/styles.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://eugene-serb.github.io/wavelovers/img/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://eugene-serb.github.io/wavelovers/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="https://eugene-serb.github.io/wavelovers/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="https://eugene-serb.github.io/wavelovers/favicon-16x16.png" />
|
||||
<link rel="manifest" href="https://eugene-serb.github.io/wavelovers/site.webmanifest" />
|
||||
|
||||
<meta name="author" content="Eugene Serb" />
|
||||
<meta name="copyright" content="Eugene Serb, 2021 – 2022" />
|
||||
<meta name="copyright" content="Wavelovers, 2022" />
|
||||
<meta name="publisher-email" content="eugene.serb@gmail.com" />
|
||||
<meta name="publisher-url" content="https://eugene-serb.github.io/" />
|
||||
<meta name="keywords" content="Eugene Serb, eugene-serb, eugene_serb, eugene.serb, Evgeniy Serb, Евгений Серб, Novorossiysk, Новороссийск, contacts, CV, resume, portfolio, repositories, services, coder, developer, software engineer, development, frontend, backend, fullstack, Gamepad, Vibration, Vibration Master, Massager, Vibrator" />
|
||||
<meta name="description" content="This is a simple vibration master." />
|
||||
<meta name="keywords" content="Wavelovers, Wave Lovers, Wavemaster, Wave Master, Vibration Master, Vibration, Massager, Vibrator, Gamepad, Gamepad Vibration, Phone Vibration, Gamepad Tester, Phone Vibration Tester, Vibration Tester, Relax, геймпад, джойстик, вибратор, вибромассажер, вибро, вибромассажёр из геймпада, тестер вибрации геймпада, тестер вибрации телефона" />
|
||||
<meta name="description" content="Wavelovers. Use your device vibration correctly. Make a massager out of a gamepad." />
|
||||
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="Eugene Serb – Vibration Master" />
|
||||
<meta property="og:site_name" content="Eugene Serb – Website" />
|
||||
<meta property="og:description" content="This is a simple vibration master." />
|
||||
<meta property="og:url" content="https://eugene-serb.github.io/" />
|
||||
<meta property="og:image" content="https://eugene-serb.github.io/img/og.png" />
|
||||
<meta property="vk:image" content="https://eugene-serb.github.io/img/og.png" />
|
||||
<meta property="og:title" content="Wavelovers" />
|
||||
<meta property="og:site_name" content="Wavelovers" />
|
||||
<meta property="og:description" content="Wavelovers. Use your device vibration correctly. Make a massager out of a gamepad." />
|
||||
<meta property="og:url" content="https://eugene-serb.github.io/wavelovers/" />
|
||||
<meta property="og:image" content="https://eugene-serb.github.io/wavelovers/img/og.png" />
|
||||
<meta property="vk:image" content="https://eugene-serb.github.io/wavelovers/img/og.png" />
|
||||
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:creator" content="@eugene_serb" />
|
||||
<meta name="twitter:title" content="Eugene Serb – Vibration Master" />
|
||||
<meta name="twitter:description" content="This is a simple vibration master." />
|
||||
<meta name="twitter:image" content="https://eugene-serb.github.io/img/og.png" />
|
||||
<meta name="twitter:title" content="Wavelovers" />
|
||||
<meta name="twitter:description" content="Wavelovers. Use your device vibration correctly. Make a massager out of a gamepad." />
|
||||
<meta name="twitter:image" content="https://eugene-serb.github.io/wavelovers/img/og.png" />
|
||||
|
||||
<meta name="google-site-verification" content="qLQbgnmQEfvprDF8WR6oL_b_Qt0R9kKcIEOfHqWlFm8" />
|
||||
<meta name="yandex-verification" content="e6e0bff7caaa7ecd" />
|
||||
@ -45,7 +48,7 @@
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-4NB4LGNNLB"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {dataLayer.push(arguments); }
|
||||
function gtag() { dataLayer.push(arguments); }
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-4NB4LGNNLB');
|
||||
@ -72,21 +75,21 @@
|
||||
<header class="header">
|
||||
<div class="header-wrapper container">
|
||||
<div class="logo-wrapper">
|
||||
<span class="logo-wrapper__logo">Eugene Serb.</span>
|
||||
<span class="logo-wrapper__logo">Wavelovers</span>
|
||||
</div>
|
||||
<nav class="menu-wrapper">
|
||||
<nav class="menu-wrapper hidden">
|
||||
<ul class="navigation">
|
||||
<li class="navigation__item">
|
||||
<a href="https://eugene-serb.github.io/" target="_self" class="navigation__link">Home</a>
|
||||
<a href="https://eugene-serb.github.io/wavelovers/" target="_self" class="navigation__link">Home</a>
|
||||
</li>
|
||||
<li class="navigation__item">
|
||||
<a href="https://eugene-serb.github.io/#skills" target="_self" class="navigation__link">Skills</a>
|
||||
<a href="https://eugene-serb.github.io/wavelovers/#faq" target="_self" class="navigation__link">FAQ</a>
|
||||
</li>
|
||||
<li class="navigation__item">
|
||||
<a href="https://eugene-serb.github.io/services.html" target="_self" class="navigation__link">Services</a>
|
||||
<a href="https://eugene-serb.github.io/wavelovers/#buy" target="_self" class="navigation__link">Buy Pro</a>
|
||||
</li>
|
||||
<li class="navigation__item">
|
||||
<a href="https://eugene-serb.github.io/projects.html" target="_self" class="navigation__link">Projects</a>
|
||||
<a href="https://eugene-serb.github.io/wavelovers/#donate" target="_self" class="navigation__link">Donate</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -94,39 +97,33 @@
|
||||
</header>
|
||||
|
||||
<main class="page container">
|
||||
<h1 class="visually-hidden">Eugene Serb – Vibration Master</h1>
|
||||
<section class="post">
|
||||
<h2 class="post__header">Vibration Master</h2>
|
||||
<div class="vibration-master">
|
||||
<div class="content-item content_bordered message-box">
|
||||
<h1 class="visually-hidden">Wavelovers</h1>
|
||||
<div class="wavelovers">
|
||||
<div id="pattern-box" class="content pattern-box">
|
||||
<div id="pattern-list" class="pattern-list"></div>
|
||||
</div>
|
||||
<div id="device-box" class="content">
|
||||
<div class="message">
|
||||
<span id="message"></span>
|
||||
</div>
|
||||
<div id="device-box" class="content-item content_bordered device-box">
|
||||
<h3>Device List</h3>
|
||||
<div id="device-list" class="device-list"></div>
|
||||
</div>
|
||||
<section class="content-item content_bordered controls-banner">
|
||||
<h3>Disclaimer</h3>
|
||||
<div class="controls-banner__list">
|
||||
<span>
|
||||
The author of the program code and the publisher is not responsible for the possible consequences of using the program. You use the program at your own risk. Please consult your doctor before using the program.
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-wrapper container">
|
||||
<div class="annotation">
|
||||
<span class="annotation__text">© 2021 – 2022 Eugene Serb. Content licensed under </span><a href="https://eugene-serb.github.io/vibration-master/LICENSE.md" target="_blank">GNU General Public License v3.0</a><br>
|
||||
<span class="annotation__text">This site is open source. </span><a href="https://github.com/eugene-serb/vibration-master/" target="_blank">Improve this page.</a>
|
||||
<span class="annotation__text">© 2022 Wavelovers. Content licensed under </span><a href="https://eugene-serb.github.io/wavelovers/LICENSE.md" target="_blank">GNU General Public License v3.0</a><br>
|
||||
<span class="annotation__text">This site is open source. </span><a href="https://github.com/eugene-serb/wavelovers/" target="_blank">Improve this page.</a>
|
||||
</div>
|
||||
<div class="annotation created-by">
|
||||
<span class="annotation__text">Created by</span><a href="https://eugene-serb.github.io/" target="_blank">Eugene Serb.</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="js/vibration-master.js"></script>
|
||||
<script src="js/scripts.js"></script>
|
||||
<script src="https://eugene-serb.github.io/js/scripts.js"></script>
|
||||
<noscript><div><img src="https://mc.yandex.ru/watch/79722217" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
||||
</body>
|
||||
|
@ -5,47 +5,11 @@
|
||||
'use strict';
|
||||
|
||||
const __PATTERNS = [
|
||||
/* Constant, 0s, 1s */
|
||||
{
|
||||
name: 'Constant Weak',
|
||||
type: 'Simple',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
duration: 1000,
|
||||
weakMagnitude: 1.0,
|
||||
strongMagnitude: 0.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Constant Strong',
|
||||
type: 'Simple',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
duration: 1000,
|
||||
weakMagnitude: 0.0,
|
||||
strongMagnitude: 1.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Constant Max',
|
||||
type: 'Simple',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
duration: 1000,
|
||||
weakMagnitude: 1.0,
|
||||
strongMagnitude: 1.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* Dotted, 0.1s, 0.1s */
|
||||
{
|
||||
name: 'Dotted Weak',
|
||||
type: 'Simple',
|
||||
icon: '😌',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 100,
|
||||
@ -58,6 +22,7 @@ const __PATTERNS = [
|
||||
{
|
||||
name: 'Dotted Strong',
|
||||
type: 'Simple',
|
||||
icon: '😉',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 100,
|
||||
@ -70,6 +35,7 @@ const __PATTERNS = [
|
||||
{
|
||||
name: 'Dotted Max',
|
||||
type: 'Simple',
|
||||
icon: '🙃',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
@ -83,6 +49,7 @@ const __PATTERNS = [
|
||||
{
|
||||
name: 'Short Dashed Weak',
|
||||
type: 'Simple',
|
||||
icon: '🙂',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 100,
|
||||
@ -95,6 +62,7 @@ const __PATTERNS = [
|
||||
{
|
||||
name: 'Short Dashed Strong',
|
||||
type: 'Simple',
|
||||
icon: '😇',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 100,
|
||||
@ -107,6 +75,7 @@ const __PATTERNS = [
|
||||
{
|
||||
name: 'Short Dashed Max',
|
||||
type: 'Simple',
|
||||
icon: '😊',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
@ -120,6 +89,7 @@ const __PATTERNS = [
|
||||
{
|
||||
name: 'Long Dashed Weak',
|
||||
type: 'Simple',
|
||||
icon: '😋',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 100,
|
||||
@ -132,6 +102,7 @@ const __PATTERNS = [
|
||||
{
|
||||
name: 'Long Dashed Strong',
|
||||
type: 'Simple',
|
||||
icon: '😜',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 100,
|
||||
@ -144,6 +115,7 @@ const __PATTERNS = [
|
||||
{
|
||||
name: 'Long Dashed Max',
|
||||
type: 'Simple',
|
||||
icon: '🤪',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
@ -153,6 +125,46 @@ const __PATTERNS = [
|
||||
},
|
||||
],
|
||||
},
|
||||
/* Constant, 0s, 1s */
|
||||
{
|
||||
name: 'Constant Weak',
|
||||
type: 'Simple',
|
||||
icon: '😏',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
duration: 1000,
|
||||
weakMagnitude: 1.0,
|
||||
strongMagnitude: 0.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Constant Strong',
|
||||
type: 'Simple',
|
||||
icon: '🤩',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
duration: 1000,
|
||||
weakMagnitude: 0.0,
|
||||
strongMagnitude: 1.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Constant Max',
|
||||
type: 'Simple',
|
||||
icon: '😍',
|
||||
pattern: [
|
||||
{
|
||||
startDelay: 0,
|
||||
duration: 1000,
|
||||
weakMagnitude: 1.0,
|
||||
strongMagnitude: 1.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
class Gamepad {
|
||||
@ -165,6 +177,7 @@ class Gamepad {
|
||||
|
||||
init = () => {
|
||||
this.id = Date.now();
|
||||
this.canVibrate = (this.unit.vibrationActuator) ? true : false;
|
||||
this.isSelected = false;
|
||||
this.isVibrating = false;
|
||||
this.isLocked = false;
|
||||
@ -187,18 +200,11 @@ class Gamepad {
|
||||
generateBox = () => {
|
||||
const $list_item = document.createElement('div');
|
||||
const $info_box = document.createElement('div');
|
||||
/*const $button = document.createElement('button');*/
|
||||
|
||||
$list_item.classList.add('list-item');
|
||||
$info_box.classList.add('list-item__info');
|
||||
/*$button.innerText = 'Select';*/
|
||||
|
||||
/*$button.addEventListener('click', () => {
|
||||
this.isSelected = !this.isSelected;
|
||||
});*/
|
||||
|
||||
$list_item.appendChild($info_box);
|
||||
/*$list_item.appendChild($button);*/
|
||||
this.$container.appendChild($list_item);
|
||||
|
||||
this.$list_item = $list_item;
|
||||
@ -207,19 +213,7 @@ class Gamepad {
|
||||
};
|
||||
draw = () => {
|
||||
this.$info_box.innerHTML = `
|
||||
<h3>#${this.unit.index + 1}. ${this.unit.id}</h3>
|
||||
<span>Vibration Actuator: ${this.unit.vibrationActuator ? 'Available' : 'missing'}</span>
|
||||
<div>
|
||||
<span>Status: </span>
|
||||
<span>${this.isVibrating ? 'Vibrating' : 'Idle'}</span>
|
||||
<span>A / B</span>
|
||||
<span>Key locked: </span>
|
||||
<span>${this.isLocked ? 'Yes' : 'No'}</span>
|
||||
<span>X + Y</span>
|
||||
<span>Mode: </span>
|
||||
<span>${this.index + 1}. ${this.library[this.index].name}</span>
|
||||
<span>LB / RB</span>
|
||||
</div>`;
|
||||
<span>Gamepad #${this.unit.index + 1}. ${this.unit.id}.</span>`;
|
||||
if (this.isSelected === true) {
|
||||
this.$list_item.classList.add('list-item_selected');
|
||||
} else {
|
||||
@ -282,6 +276,11 @@ class Gamepad {
|
||||
this.cooldown = Date.now() + 500;
|
||||
};
|
||||
};
|
||||
|
||||
change = (index) => {
|
||||
this.index = index;
|
||||
this.pattern = this.library[this.index].pattern;
|
||||
};
|
||||
};
|
||||
|
||||
class VibrationMaster {
|
||||
@ -290,6 +289,8 @@ class VibrationMaster {
|
||||
};
|
||||
init = () => {
|
||||
this.#DOMs();
|
||||
this.patterns = __PATTERNS;
|
||||
this.print(this.patterns);
|
||||
|
||||
if (!this.checkGamepadSupport()) {
|
||||
console.log(`This browser does not support of gamepads.`);
|
||||
@ -302,25 +303,23 @@ class VibrationMaster {
|
||||
|
||||
this.gamepads = [];
|
||||
this.#eventListeners();
|
||||
|
||||
this.interval = setInterval(this.eventLoop, 1);
|
||||
};
|
||||
|
||||
eventLoop = () => {
|
||||
this.update();
|
||||
this.draw();
|
||||
this.eventHandler();
|
||||
};
|
||||
update = () => {
|
||||
if (this.gamepads.length > 0) {
|
||||
this.$MESSAGE_BOX.classList.add('hidden');
|
||||
this.$DEVICE_BOX.classList.remove('hidden');
|
||||
this.$MESSAGE.classList.add('hidden');
|
||||
this.$DEVICE_LIST.classList.remove('hidden');
|
||||
this.gamepads.forEach(gamepad => {
|
||||
gamepad.update();
|
||||
});
|
||||
} else {
|
||||
this.$MESSAGE_BOX.classList.remove('hidden');
|
||||
this.$DEVICE_BOX.classList.add('hidden');
|
||||
this.$MESSAGE.classList.remove('hidden');
|
||||
this.$DEVICE_LIST.classList.add('hidden');
|
||||
};
|
||||
};
|
||||
draw = () => {
|
||||
@ -333,7 +332,7 @@ class VibrationMaster {
|
||||
eventHandler = () => {
|
||||
if (this.gamepads.length > 0) {
|
||||
this.gamepads.forEach(gamepad => {
|
||||
if (gamepad.unit.vibrationActuator) {
|
||||
if (gamepad.canVibrate === true) {
|
||||
if (gamepad.unit.buttons[2].pressed === true &&
|
||||
gamepad.unit.buttons[3].pressed === true) {
|
||||
gamepad.lock();
|
||||
@ -359,19 +358,76 @@ class VibrationMaster {
|
||||
};
|
||||
};
|
||||
|
||||
print = (patterns) => {
|
||||
this.$PATTERN_LIST.innerHTML = '';
|
||||
patterns.forEach((pattern, index) => {
|
||||
const $container = document.createElement('div');
|
||||
const $icon = document.createElement('span');
|
||||
const $name = document.createElement('span');
|
||||
|
||||
$container.classList.add('pattern-item');
|
||||
$icon.classList.add('pattern-item__icon');
|
||||
$name.classList.add('pattern-item__name');
|
||||
|
||||
$icon.innerHTML = pattern.icon;
|
||||
$name.innerHTML = pattern.name;
|
||||
|
||||
$container.appendChild($icon);
|
||||
$container.appendChild($name);
|
||||
|
||||
$container.addEventListener('click', () => this.change(index));
|
||||
|
||||
this.$PATTERN_LIST.appendChild($container);
|
||||
pattern['container'] = $container;
|
||||
});
|
||||
};
|
||||
change = (index) => {
|
||||
if (this.gamepads.length > 0) {
|
||||
this.gamepads.forEach(gamepad => {
|
||||
this.unselect();
|
||||
if (gamepad.index === index &&
|
||||
gamepad.isVibrating === true) {
|
||||
gamepad.isVibrating = false;
|
||||
gamepad.reset();
|
||||
} else {
|
||||
gamepad.change(index);
|
||||
gamepad.vibrate();
|
||||
this.select(index);
|
||||
};
|
||||
});
|
||||
} else {
|
||||
console.log('No connected devices...');
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
unselect = () => {
|
||||
this.patterns.forEach(pattern => {
|
||||
pattern['container'].classList.remove('pattern-item__selected');
|
||||
});
|
||||
};
|
||||
select = (index) => {
|
||||
this.patterns[index]['container'].classList.add('pattern-item__selected');
|
||||
};
|
||||
|
||||
checkGamepadSupport = () => {
|
||||
return 'getGamepads' in window.navigator;
|
||||
};
|
||||
|
||||
#DOMs = () => {
|
||||
this.$MESSAGE = document.querySelector('#message');
|
||||
this.$MESSAGE_BOX = document.querySelector('.message-box');
|
||||
this.$DEVICE_LIST = document.querySelector('#device-list');
|
||||
this.$DEVICE_BOX = document.querySelector('#device-box');
|
||||
this.$MESSAGE = document.querySelector('#message');
|
||||
this.$DEVICE_LIST = document.querySelector('#device-list');
|
||||
this.$PATTERN_BOX = document.querySelector('#pattern-box');
|
||||
this.$PATTERN_LIST = document.querySelector('#pattern-list');
|
||||
};
|
||||
#eventListeners = () => {
|
||||
window.addEventListener('gamepadconnected', (event) => {
|
||||
this.gamepads.push(new Gamepad(event.gamepad, this.$DEVICE_LIST, __PATTERNS));
|
||||
if (this.gamepads.length > 1) {
|
||||
return;
|
||||
} else {
|
||||
this.gamepads.push(new Gamepad(event.gamepad, this.$DEVICE_LIST, this.patterns));
|
||||
};
|
||||
});
|
||||
window.addEventListener('gamepaddisconnected', (event) => {
|
||||
this.gamepads.forEach((gamepad, index) => {
|
20
site.webmanifest
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://eugene-serb.github.io/wavelovers/img/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "https://eugene-serb.github.io/wavelovers/img/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|