Improve settings
This commit is contained in:
parent
2509199abc
commit
81a8834c4d
19 changed files with 439 additions and 123 deletions
|
|
@ -47,6 +47,7 @@
|
|||
"sass-loader": "^9.0.2",
|
||||
"sdf-2d": "^0.6.0",
|
||||
"shared": "0.0.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"socket.io-client": "^2.3.1",
|
||||
"source-map-loader": "^1.1.0",
|
||||
"svg-url-loader": "^6.0.0",
|
||||
|
|
|
|||
|
|
@ -46,23 +46,44 @@
|
|||
</form>
|
||||
</section>
|
||||
|
||||
<img id="open-settings" class="icon" alt="open-settings" src="static/settings.svg" />
|
||||
|
||||
<div id="settings-container">
|
||||
<section id="settings">
|
||||
<h2>Settings</h2>
|
||||
<input id="enable-relative-movement" checked type="checkbox" />
|
||||
<label for="enable-relative-movement">Enable relative movement</label>
|
||||
<button id="close-settings">Back</button>
|
||||
<label for="enable-relative-movement">
|
||||
<input id="enable-relative-movement" type="checkbox" />
|
||||
<img alt="a dashed circle" src="static/circle.svg" />
|
||||
</label>
|
||||
|
||||
<label for="enable-vibration">
|
||||
<input id="enable-vibration" type="checkbox" />
|
||||
<img alt="vibrating mobile" src="static/vibrate.svg" />
|
||||
</label>
|
||||
|
||||
<label for="enable-sounds">
|
||||
<input id="enable-sounds" type="checkbox" />
|
||||
<img alt="speaker with sound waves" src="static/volume.svg" />
|
||||
</label>
|
||||
|
||||
<img id="logout" alt="logout" src="static/logout.svg" />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="toggle-settings-container">
|
||||
<img
|
||||
id="toggle-settings"
|
||||
class="icon"
|
||||
alt="toggle-settings"
|
||||
src="static/settings.svg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="full-screen-controllers icon"
|
||||
class="full-screen-controllers"
|
||||
id="minimize"
|
||||
alt="minimize-application"
|
||||
src="static/minimize.svg"
|
||||
/>
|
||||
<img
|
||||
class="full-screen-controllers icon"
|
||||
class="full-screen-controllers"
|
||||
id="maximize"
|
||||
alt="maximize-application"
|
||||
src="static/maximize.svg"
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ import { JoinFormHandler } from './scripts/join-form-handler';
|
|||
import { handleFullScreen } from './scripts/handle-full-screen';
|
||||
import { Game } from './scripts/game';
|
||||
import { PlayerCharacterView } from './scripts/objects/player-character-view';
|
||||
|
||||
import '../static/settings.svg';
|
||||
import '../static/minimize.svg';
|
||||
import '../static/maximize.svg';
|
||||
import { handleInsights } from './scripts/handle-insights';
|
||||
import { getInsightsFromRenderer } from './scripts/get-insights-from-renderer';
|
||||
import { Renderer } from 'sdf-2d';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import { OptionsHandler } from './scripts/options-handler';
|
||||
|
||||
glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
|
|
@ -32,40 +34,44 @@ overrideDeserialization(PlanetBase, PlanetView);
|
|||
overrideDeserialization(LampBase, LampView);
|
||||
overrideDeserialization(ProjectileBase, ProjectileView);
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
const landingUI = document.querySelector('#landing-ui') as HTMLElement;
|
||||
const joinGameForm = document.querySelector('#join-game-form') as HTMLFormElement;
|
||||
const serverContainer = document.querySelector('#server-container') as HTMLElement;
|
||||
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
|
||||
const overlay = document.querySelector('#overlay') as HTMLElement;
|
||||
const settings = document.querySelector('#settings') as HTMLElement;
|
||||
const openSettings = document.querySelector('#open-settings') as HTMLElement;
|
||||
const closeSettings = document.querySelector('#close-settings') as HTMLElement;
|
||||
const toggleSettingsButton = document.querySelector(
|
||||
'#toggle-settings-container',
|
||||
) as HTMLElement;
|
||||
const minimize = document.querySelector('#minimize') as HTMLElement;
|
||||
const maximize = document.querySelector('#maximize') as HTMLElement;
|
||||
const enableRelativeMovementCheckbox = document.querySelector(
|
||||
const logoutButton = document.querySelector('#logout') as HTMLElement;
|
||||
const enableRelativeMovement = document.querySelector(
|
||||
'#enable-relative-movement',
|
||||
) as HTMLElement;
|
||||
) as HTMLInputElement;
|
||||
const enableSounds = document.querySelector('#enable-sounds') as HTMLInputElement;
|
||||
const enableVibration = document.querySelector('#enable-vibration') as HTMLInputElement;
|
||||
|
||||
openSettings.addEventListener('click', () => (settings.style.visibility = 'visible'));
|
||||
closeSettings.addEventListener('click', () => (settings.style.visibility = 'hidden'));
|
||||
|
||||
const background = new LandingPageBackground(canvas);
|
||||
const joinHandler = new JoinFormHandler(joinGameForm, serverContainer);
|
||||
handleFullScreen(minimize, maximize);
|
||||
|
||||
let backgroundRenderer = await background.renderer;
|
||||
let isInGame = false;
|
||||
let game: Game;
|
||||
const getFrameData = () => {
|
||||
|
||||
const startInsights = (getRenderer: () => Renderer | undefined) => {
|
||||
const { vendor, renderer } = getInsightsFromRenderer(getRenderer());
|
||||
handleInsights(
|
||||
{
|
||||
vendor,
|
||||
renderer,
|
||||
referrer: document.referrer,
|
||||
connection: (navigator as any)?.connection?.effectiveType,
|
||||
devicePixelRatio: devicePixelRatio,
|
||||
},
|
||||
() => {
|
||||
const {
|
||||
fps,
|
||||
renderScale,
|
||||
lightScale,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
} = getInsightsFromRenderer(isInGame ? game.renderer : backgroundRenderer);
|
||||
} = getInsightsFromRenderer(getRenderer());
|
||||
|
||||
return {
|
||||
isInGame,
|
||||
|
|
@ -75,26 +81,75 @@ const main = async () => {
|
|||
canvasWidth,
|
||||
canvasHeight,
|
||||
};
|
||||
};
|
||||
const { vendor, renderer } = getInsightsFromRenderer(backgroundRenderer);
|
||||
handleInsights(
|
||||
{
|
||||
vendor,
|
||||
renderer,
|
||||
referrer: document.referrer,
|
||||
connection: (navigator as any)?.connection?.effectiveType,
|
||||
devicePixelRatio: devicePixelRatio,
|
||||
},
|
||||
getFrameData,
|
||||
);
|
||||
};
|
||||
|
||||
const toggleSettings = () =>
|
||||
(settings.className = settings.className === 'open' ? '' : 'open');
|
||||
|
||||
const applyServerContainerShadows = () => {
|
||||
const { scrollHeight, clientHeight, scrollTop } = serverContainer;
|
||||
console.log(scrollHeight, clientHeight, scrollTop);
|
||||
if (scrollHeight > clientHeight) {
|
||||
serverContainer.className = 'scroll';
|
||||
if (scrollTop === 0) {
|
||||
serverContainer.className += ' top';
|
||||
} else if (scrollTop + clientHeight === scrollHeight) {
|
||||
serverContainer.className += ' bottom';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
let game: Game;
|
||||
|
||||
handleFullScreen(minimize, maximize);
|
||||
toggleSettingsButton.addEventListener('click', toggleSettings);
|
||||
|
||||
new ResizeObserver(applyServerContainerShadows).observe(serverContainer);
|
||||
serverContainer.addEventListener('scroll', applyServerContainerShadows);
|
||||
|
||||
OptionsHandler.initialize({
|
||||
relativeMovementEnabled: enableRelativeMovement,
|
||||
soundsEnabled: enableSounds,
|
||||
vibrationEnabled: enableVibration,
|
||||
});
|
||||
|
||||
logoutButton.addEventListener('click', () => {
|
||||
game.destroy();
|
||||
toggleSettings();
|
||||
});
|
||||
window.onpopstate = () => game.destroy();
|
||||
|
||||
let backgroundRenderer: Renderer | undefined;
|
||||
startInsights(() => (isInGame ? game.renderer : backgroundRenderer));
|
||||
|
||||
for (;;) {
|
||||
landingUI.style.visibility = 'inherit';
|
||||
logoutButton.style.visibility = 'hidden';
|
||||
|
||||
const background = new LandingPageBackground(canvas);
|
||||
const joinHandler = new JoinFormHandler(joinGameForm, serverContainer);
|
||||
|
||||
backgroundRenderer = await background.renderer;
|
||||
|
||||
const playerDecision = await joinHandler.getPlayerDecision();
|
||||
landingUI.style.display = 'none';
|
||||
if (!history.state) {
|
||||
history.pushState(true, '');
|
||||
}
|
||||
|
||||
landingUI.style.visibility = 'hidden';
|
||||
|
||||
background.destroy();
|
||||
|
||||
logoutButton.style.visibility = 'inherit';
|
||||
game = new Game(playerDecision, canvas, overlay);
|
||||
isInGame = true;
|
||||
await game.start();
|
||||
isInGame = false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert(e);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { CommandGenerator, MoveActionCommand } from 'shared';
|
||||
import { OptionsHandler } from '../../options-handler';
|
||||
|
||||
export class KeyboardListener extends CommandGenerator {
|
||||
private keysDown: Set<string> = new Set();
|
||||
|
|
@ -34,7 +35,9 @@ export class KeyboardListener extends CommandGenerator {
|
|||
if (vec2.squaredLength(movement) > 0) {
|
||||
vec2.normalize(movement, movement);
|
||||
}
|
||||
this.sendCommandToSubcribers(new MoveActionCommand(movement, true));
|
||||
this.sendCommandToSubcribers(
|
||||
new MoveActionCommand(movement, OptionsHandler.options.relativeMovementEnabled),
|
||||
);
|
||||
}
|
||||
|
||||
private normalize(key: string): string {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
MoveActionCommand,
|
||||
} from 'shared';
|
||||
import { Game } from '../../game';
|
||||
import { OptionsHandler } from '../../options-handler';
|
||||
|
||||
export class TouchListener extends CommandGenerator {
|
||||
private previousPosition = vec2.create();
|
||||
|
|
@ -38,7 +39,9 @@ export class TouchListener extends CommandGenerator {
|
|||
|
||||
if (vec2.squaredLength(movement) > 0) {
|
||||
vec2.normalize(movement, movement);
|
||||
this.sendCommandToSubcribers(new MoveActionCommand(movement, false));
|
||||
this.sendCommandToSubcribers(
|
||||
new MoveActionCommand(movement, OptionsHandler.options.relativeMovementEnabled),
|
||||
);
|
||||
}
|
||||
|
||||
this.previousPosition = position;
|
||||
|
|
@ -46,7 +49,12 @@ export class TouchListener extends CommandGenerator {
|
|||
|
||||
target.addEventListener('touchend', (event: TouchEvent) => {
|
||||
event.preventDefault();
|
||||
this.sendCommandToSubcribers(new MoveActionCommand(vec2.create(), false));
|
||||
this.sendCommandToSubcribers(
|
||||
new MoveActionCommand(
|
||||
vec2.create(),
|
||||
OptionsHandler.options.relativeMovementEnabled,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import { TouchListener } from './commands/generators/touch-listener';
|
|||
import { CommandReceiverSocket } from './commands/receivers/command-receiver-socket';
|
||||
import { PlayerDecision } from './join-form-handler';
|
||||
import { GameObjectContainer } from './objects/game-object-container';
|
||||
import { options } from './options';
|
||||
import { OptionsHandler } from './options-handler';
|
||||
import { BlobShape } from './shapes/blob-shape';
|
||||
import { PlanetShape } from './shapes/planet-shape';
|
||||
|
||||
|
|
@ -48,7 +48,6 @@ export class Game {
|
|||
private readonly canvas: HTMLCanvasElement,
|
||||
private readonly overlay: HTMLElement,
|
||||
) {
|
||||
this.start();
|
||||
const progressBar = document.createElement('div');
|
||||
progressBar.className = 'planet-progress';
|
||||
overlay.appendChild(progressBar);
|
||||
|
|
@ -71,7 +70,7 @@ export class Game {
|
|||
const command = deserialize(serialized);
|
||||
if (command instanceof PlayerDiedCommand) {
|
||||
this.deadTimeout = command.timeout;
|
||||
if (options.vibrationEnabled) {
|
||||
if (OptionsHandler.options.vibrationEnabled) {
|
||||
navigator.vibrate(150);
|
||||
}
|
||||
this.overlay.appendChild(this.announcmentText);
|
||||
|
|
@ -112,10 +111,10 @@ export class Game {
|
|||
);
|
||||
}
|
||||
|
||||
private async start(): Promise<void> {
|
||||
public async start(): Promise<void> {
|
||||
const noiseTexture = await renderNoise([256, 256], 2, 1);
|
||||
this.setupCommunication(this.playerDecision.server);
|
||||
runAnimation(
|
||||
await runAnimation(
|
||||
this.canvas,
|
||||
[
|
||||
{
|
||||
|
|
@ -162,6 +161,8 @@ export class Game {
|
|||
},
|
||||
},
|
||||
);
|
||||
this.socket.close();
|
||||
this.overlay.innerHTML = '';
|
||||
}
|
||||
|
||||
public displayToWorldCoordinates(p: vec2): vec2 {
|
||||
|
|
@ -179,6 +180,11 @@ export class Game {
|
|||
);
|
||||
}
|
||||
|
||||
private isActive = true;
|
||||
public destroy() {
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
private announcmentText = document.createElement('h2');
|
||||
private gameLoop(
|
||||
renderer: Renderer,
|
||||
|
|
@ -196,6 +202,6 @@ export class Game {
|
|||
this.gameObjects.stepObjects(deltaTime);
|
||||
this.gameObjects.drawObjects(this.renderer, this.overlay);
|
||||
|
||||
return true;
|
||||
return this.isActive;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
CharacterTeam,
|
||||
settings,
|
||||
} from 'shared';
|
||||
import { options } from '../options';
|
||||
import { OptionsHandler } from '../options-handler';
|
||||
|
||||
import { BlobShape } from '../shapes/blob-shape';
|
||||
import { ViewObject } from './view-object';
|
||||
|
|
@ -50,7 +50,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
|||
this.healthElement.style.width = this.health + '%';
|
||||
if (this.previousHealth > this.health) {
|
||||
this.previousHealth = this.health;
|
||||
if (options.vibrationEnabled) {
|
||||
if (OptionsHandler.options.vibrationEnabled) {
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
51
frontend/src/scripts/options-handler.ts
Normal file
51
frontend/src/scripts/options-handler.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
interface Options {
|
||||
vibrationEnabled: boolean;
|
||||
soundsEnabled: boolean;
|
||||
relativeMovementEnabled: boolean;
|
||||
}
|
||||
|
||||
export abstract class OptionsHandler {
|
||||
private static initialized = false;
|
||||
private static _options: Options = {
|
||||
vibrationEnabled: true,
|
||||
soundsEnabled: true,
|
||||
relativeMovementEnabled: false,
|
||||
};
|
||||
|
||||
public static initialize(
|
||||
inputElements: { [k in Extract<keyof Options, string>]: HTMLInputElement },
|
||||
) {
|
||||
if (localStorage.getItem('options')) {
|
||||
const stored: Partial<Options> | null = JSON.parse(
|
||||
localStorage.getItem('options')!,
|
||||
);
|
||||
this._options = {
|
||||
...this._options,
|
||||
...stored,
|
||||
};
|
||||
}
|
||||
|
||||
for (const k in inputElements) {
|
||||
const element = inputElements[k as keyof Options];
|
||||
element.checked = OptionsHandler._options[k as keyof Options];
|
||||
element.addEventListener('change', function () {
|
||||
OptionsHandler._options[k as keyof Options] = this.checked;
|
||||
OptionsHandler.save();
|
||||
});
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
private static save() {
|
||||
localStorage.setItem('options', JSON.stringify(this._options));
|
||||
}
|
||||
|
||||
public static get options(): Options {
|
||||
if (!this.initialized) {
|
||||
throw new Error('Uninitialized');
|
||||
}
|
||||
|
||||
return this._options;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export const options = {
|
||||
vibrationEnabled: true,
|
||||
};
|
||||
26
frontend/src/styles/button.scss
Normal file
26
frontend/src/styles/button.scss
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
@import './vars.scss';
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 2rem;
|
||||
font-family: 'Comfortaa', sans-serif;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin: auto;
|
||||
|
||||
$ascend: 6px;
|
||||
padding: $medium-padding 0 0 0;
|
||||
margin-bottom: $ascend;
|
||||
transition: transform $animation-time, padding-bottom $animation-time,
|
||||
margin-bottom $animation-time;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: none;
|
||||
color: $accent;
|
||||
transform: translateY(-$ascend);
|
||||
padding-bottom: $ascend;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ form {
|
|||
padding: $small-padding;
|
||||
border: $border;
|
||||
cursor: pointer;
|
||||
margin: $border-width-focused - $border-width;
|
||||
margin: $border-width-focused - $border-width $small-padding / 2;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
|
|
@ -107,27 +107,8 @@ form {
|
|||
&:checked + label {
|
||||
border-color: $accent;
|
||||
border-width: $border-width-focused;
|
||||
margin: 0;
|
||||
margin-right: 0 $small-padding / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 2rem;
|
||||
font-family: 'Comfortaa', sans-serif;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin: auto;
|
||||
padding-top: $medium-padding;
|
||||
transition: transform $animation-time;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: none;
|
||||
color: $accent;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
@import './vars.scss';
|
||||
@import './button.scss';
|
||||
@import './form.scss';
|
||||
@import './mixins.scss';
|
||||
@import './settings.scss';
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@300&family=Open+Sans&display=swap');
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
|
|
@ -22,8 +25,11 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
img {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
&,
|
||||
* {
|
||||
font-family: 'Comfortaa', sans-serif;
|
||||
|
|
@ -31,6 +37,10 @@ h2 {
|
|||
font-size: 6rem;
|
||||
text-align: center;
|
||||
padding-bottom: $medium-padding;
|
||||
|
||||
@media (max-height: $height-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
|
|
@ -131,30 +141,29 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
#open-settings {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
#server-container {
|
||||
max-height: 30vh;
|
||||
overflow-y: auto;
|
||||
|
||||
animation: spin 32s linear infinite;
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
&::-webkit-scrollbar-track,
|
||||
&::-webkit-scrollbar {
|
||||
background-color: transparent;
|
||||
width: 2px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: $accent;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.icon {
|
||||
box-sizing: content-box;
|
||||
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
padding: $medium-padding;
|
||||
@include square(48px);
|
||||
@media (max-width: $breakpoint) {
|
||||
@include square(32px);
|
||||
padding: $small-padding;
|
||||
&.scroll {
|
||||
box-shadow: inset 0 -8px 8px -8px rgba(0, 0, 0, 0.4),
|
||||
inset 0 8px 8px -8px rgba(0, 0, 0, 0.4);
|
||||
&.top {
|
||||
box-shadow: inset 0 -8px 8px -8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
&.bottom {
|
||||
box-shadow: inset 0 8px 8px -8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,17 +171,21 @@ body {
|
|||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
box-sizing: content-box;
|
||||
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
padding: $medium-padding;
|
||||
@include square($large-icon);
|
||||
@media (max-width: $breakpoint) {
|
||||
@include square($small-icon);
|
||||
padding: $small-padding;
|
||||
}
|
||||
|
||||
#settings {
|
||||
@include background;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
padding: $large-padding;
|
||||
&:not(:first-child) {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@
|
|||
@mixin background {
|
||||
backdrop-filter: blur(24px);
|
||||
@supports not (backdrop-filter: blur(24px)) {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
122
frontend/src/styles/settings.scss
Normal file
122
frontend/src/styles/settings.scss
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
@import './vars.scss';
|
||||
@import './mixins.scss';
|
||||
|
||||
#toggle-settings-container,
|
||||
#settings-container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#toggle-settings-container {
|
||||
padding: $medium-padding;
|
||||
cursor: pointer;
|
||||
|
||||
#toggle-settings {
|
||||
@include square($large-icon);
|
||||
@media (max-width: $breakpoint) {
|
||||
@include square($small-icon);
|
||||
}
|
||||
|
||||
animation: spin 32s linear infinite;
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#settings-container {
|
||||
top: $medium-padding + $large-icon + $small-padding;
|
||||
@media (max-width: $breakpoint) {
|
||||
top: 0;
|
||||
right: $medium-padding + $small-icon + $small-padding;
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 $medium-padding $medium-padding $medium-padding;
|
||||
|
||||
transform: translateY(-100%);
|
||||
@media (max-width: $breakpoint) {
|
||||
flex-direction: row-reverse;
|
||||
transform: translateX(100%);
|
||||
padding: $medium-padding 0 $medium-padding $medium-padding;
|
||||
}
|
||||
|
||||
opacity: 0;
|
||||
transition: transform $animation-time, opacity $animation-time;
|
||||
|
||||
&.open {
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
transition: opacity $animation-time;
|
||||
margin-bottom: $small-padding;
|
||||
@include square($large-icon);
|
||||
@media (max-width: $breakpoint) {
|
||||
@include square($small-icon);
|
||||
margin-bottom: 0;
|
||||
margin-left: $small-padding;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
width: 0;
|
||||
height: 0;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:checked) + img {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: ' ';
|
||||
background: white;
|
||||
position: absolute;
|
||||
|
||||
$height: 4px;
|
||||
width: $height;
|
||||
top: $height / 2;
|
||||
|
||||
right: 0;
|
||||
transform-origin: top right;
|
||||
border-radius: 100px;
|
||||
height: 0;
|
||||
transform: rotate(45deg) translateY(17%);
|
||||
transition: height $animation-time;
|
||||
|
||||
@media (max-width: $breakpoint) {
|
||||
$height: 2px;
|
||||
width: $height;
|
||||
top: $height / 2;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:checked):after {
|
||||
height: $large-icon;
|
||||
@media (max-width: $breakpoint) {
|
||||
height: $small-icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,5 +8,8 @@ $small-padding: 12px;
|
|||
$medium-padding: 24px;
|
||||
$large-padding: 64px;
|
||||
$border-radius: 15px;
|
||||
$animation-time: 200ms;
|
||||
$animation-time: 200ms ease;
|
||||
$breakpoint: 700px;
|
||||
$height-breakpoint: 500px;
|
||||
$large-icon: 48px;
|
||||
$small-icon: 32px;
|
||||
|
|
|
|||
11
frontend/static/circle.svg
Normal file
11
frontend/static/circle.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" stroke-width="1.5" stroke="#ffffff" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M8.56 3.69a9 9 0 0 0 -2.92 1.95" />
|
||||
<path d="M3.69 8.56a9 9 0 0 0 -.69 3.44" />
|
||||
<path d="M3.69 15.44a9 9 0 0 0 1.95 2.92" />
|
||||
<path d="M8.56 20.31a9 9 0 0 0 3.44 .69" />
|
||||
<path d="M15.44 20.31a9 9 0 0 0 2.92 -1.95" />
|
||||
<path d="M20.31 15.44a9 9 0 0 0 .69 -3.44" />
|
||||
<path d="M20.31 8.56a9 9 0 0 0 -1.95 -2.92" />
|
||||
<path d="M15.44 3.69a9 9 0 0 0 -3.44 -.69" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 619 B |
5
frontend/static/logout.svg
Normal file
5
frontend/static/logout.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" stroke-width="1.5" stroke="#ffffff" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2" />
|
||||
<path d="M7 12h14l-3 -3m0 6l3 -3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
7
frontend/static/vibrate.svg
Normal file
7
frontend/static/vibrate.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" stroke-width="1.5" stroke="#FFFFFF" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="4" y="4" width="10" height="16" rx="1" />
|
||||
<line x1="8" y1="5" x2="10" y2="5" />
|
||||
<line x1="9" y1="17" x2="9" y2="17.01" />
|
||||
<path d="M20 6l-2 3l2 3l-2 3l2 3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 415 B |
6
frontend/static/volume.svg
Normal file
6
frontend/static/volume.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" stroke-width="1.5" stroke="#ffffff" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M15 8a5 5 0 0 1 0 8" />
|
||||
<path d="M17.7 5a9 9 0 0 1 0 14" />
|
||||
<path d="M6 15h-2a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h2l3.5 -4.5a.8 .8 0 0 1 1.5 .5v14a.8 .8 0 0 1 -1.5 .5l-3.5 -4.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 433 B |
Loading…
Add table
Add a link
Reference in a new issue