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",
|
"sass-loader": "^9.0.2",
|
||||||
"sdf-2d": "^0.6.0",
|
"sdf-2d": "^0.6.0",
|
||||||
"shared": "0.0.0",
|
"shared": "0.0.0",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"socket.io-client": "^2.3.1",
|
"socket.io-client": "^2.3.1",
|
||||||
"source-map-loader": "^1.1.0",
|
"source-map-loader": "^1.1.0",
|
||||||
"svg-url-loader": "^6.0.0",
|
"svg-url-loader": "^6.0.0",
|
||||||
|
|
|
||||||
|
|
@ -46,23 +46,44 @@
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<img id="open-settings" class="icon" alt="open-settings" src="static/settings.svg" />
|
<div id="settings-container">
|
||||||
|
|
||||||
<section id="settings">
|
<section id="settings">
|
||||||
<h2>Settings</h2>
|
<label for="enable-relative-movement">
|
||||||
<input id="enable-relative-movement" checked type="checkbox" />
|
<input id="enable-relative-movement" type="checkbox" />
|
||||||
<label for="enable-relative-movement">Enable relative movement</label>
|
<img alt="a dashed circle" src="static/circle.svg" />
|
||||||
<button id="close-settings">Back</button>
|
</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>
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="toggle-settings-container">
|
||||||
|
<img
|
||||||
|
id="toggle-settings"
|
||||||
|
class="icon"
|
||||||
|
alt="toggle-settings"
|
||||||
|
src="static/settings.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="full-screen-controllers icon"
|
class="full-screen-controllers"
|
||||||
id="minimize"
|
id="minimize"
|
||||||
alt="minimize-application"
|
alt="minimize-application"
|
||||||
src="static/minimize.svg"
|
src="static/minimize.svg"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
class="full-screen-controllers icon"
|
class="full-screen-controllers"
|
||||||
id="maximize"
|
id="maximize"
|
||||||
alt="maximize-application"
|
alt="maximize-application"
|
||||||
src="static/maximize.svg"
|
src="static/maximize.svg"
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,14 @@ import { JoinFormHandler } from './scripts/join-form-handler';
|
||||||
import { handleFullScreen } from './scripts/handle-full-screen';
|
import { handleFullScreen } from './scripts/handle-full-screen';
|
||||||
import { Game } from './scripts/game';
|
import { Game } from './scripts/game';
|
||||||
import { PlayerCharacterView } from './scripts/objects/player-character-view';
|
import { PlayerCharacterView } from './scripts/objects/player-character-view';
|
||||||
|
|
||||||
import '../static/settings.svg';
|
import '../static/settings.svg';
|
||||||
import '../static/minimize.svg';
|
import '../static/minimize.svg';
|
||||||
import '../static/maximize.svg';
|
import '../static/maximize.svg';
|
||||||
import { handleInsights } from './scripts/handle-insights';
|
import { handleInsights } from './scripts/handle-insights';
|
||||||
import { getInsightsFromRenderer } from './scripts/get-insights-from-renderer';
|
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);
|
glMatrix.setMatrixArrayType(Array);
|
||||||
|
|
||||||
|
|
@ -32,40 +34,44 @@ overrideDeserialization(PlanetBase, PlanetView);
|
||||||
overrideDeserialization(LampBase, LampView);
|
overrideDeserialization(LampBase, LampView);
|
||||||
overrideDeserialization(ProjectileBase, ProjectileView);
|
overrideDeserialization(ProjectileBase, ProjectileView);
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
try {
|
|
||||||
const landingUI = document.querySelector('#landing-ui') as HTMLElement;
|
const landingUI = document.querySelector('#landing-ui') as HTMLElement;
|
||||||
const joinGameForm = document.querySelector('#join-game-form') as HTMLFormElement;
|
const joinGameForm = document.querySelector('#join-game-form') as HTMLFormElement;
|
||||||
const serverContainer = document.querySelector('#server-container') as HTMLElement;
|
const serverContainer = document.querySelector('#server-container') as HTMLElement;
|
||||||
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
|
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
|
||||||
const overlay = document.querySelector('#overlay') as HTMLElement;
|
const overlay = document.querySelector('#overlay') as HTMLElement;
|
||||||
const settings = document.querySelector('#settings') as HTMLElement;
|
const settings = document.querySelector('#settings') as HTMLElement;
|
||||||
const openSettings = document.querySelector('#open-settings') as HTMLElement;
|
const toggleSettingsButton = document.querySelector(
|
||||||
const closeSettings = document.querySelector('#close-settings') as HTMLElement;
|
'#toggle-settings-container',
|
||||||
|
) as HTMLElement;
|
||||||
const minimize = document.querySelector('#minimize') as HTMLElement;
|
const minimize = document.querySelector('#minimize') as HTMLElement;
|
||||||
const maximize = document.querySelector('#maximize') 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',
|
'#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 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 {
|
const {
|
||||||
fps,
|
fps,
|
||||||
renderScale,
|
renderScale,
|
||||||
lightScale,
|
lightScale,
|
||||||
canvasWidth,
|
canvasWidth,
|
||||||
canvasHeight,
|
canvasHeight,
|
||||||
} = getInsightsFromRenderer(isInGame ? game.renderer : backgroundRenderer);
|
} = getInsightsFromRenderer(getRenderer());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isInGame,
|
isInGame,
|
||||||
|
|
@ -75,26 +81,75 @@ const main = async () => {
|
||||||
canvasWidth,
|
canvasWidth,
|
||||||
canvasHeight,
|
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();
|
const playerDecision = await joinHandler.getPlayerDecision();
|
||||||
landingUI.style.display = 'none';
|
if (!history.state) {
|
||||||
|
history.pushState(true, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
landingUI.style.visibility = 'hidden';
|
||||||
|
|
||||||
background.destroy();
|
background.destroy();
|
||||||
|
|
||||||
|
logoutButton.style.visibility = 'inherit';
|
||||||
game = new Game(playerDecision, canvas, overlay);
|
game = new Game(playerDecision, canvas, overlay);
|
||||||
isInGame = true;
|
isInGame = true;
|
||||||
|
await game.start();
|
||||||
|
isInGame = false;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
alert(e);
|
alert(e);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { CommandGenerator, MoveActionCommand } from 'shared';
|
import { CommandGenerator, MoveActionCommand } from 'shared';
|
||||||
|
import { OptionsHandler } from '../../options-handler';
|
||||||
|
|
||||||
export class KeyboardListener extends CommandGenerator {
|
export class KeyboardListener extends CommandGenerator {
|
||||||
private keysDown: Set<string> = new Set();
|
private keysDown: Set<string> = new Set();
|
||||||
|
|
@ -34,7 +35,9 @@ export class KeyboardListener extends CommandGenerator {
|
||||||
if (vec2.squaredLength(movement) > 0) {
|
if (vec2.squaredLength(movement) > 0) {
|
||||||
vec2.normalize(movement, movement);
|
vec2.normalize(movement, movement);
|
||||||
}
|
}
|
||||||
this.sendCommandToSubcribers(new MoveActionCommand(movement, true));
|
this.sendCommandToSubcribers(
|
||||||
|
new MoveActionCommand(movement, OptionsHandler.options.relativeMovementEnabled),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private normalize(key: string): string {
|
private normalize(key: string): string {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
MoveActionCommand,
|
MoveActionCommand,
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
import { Game } from '../../game';
|
import { Game } from '../../game';
|
||||||
|
import { OptionsHandler } from '../../options-handler';
|
||||||
|
|
||||||
export class TouchListener extends CommandGenerator {
|
export class TouchListener extends CommandGenerator {
|
||||||
private previousPosition = vec2.create();
|
private previousPosition = vec2.create();
|
||||||
|
|
@ -38,7 +39,9 @@ export class TouchListener extends CommandGenerator {
|
||||||
|
|
||||||
if (vec2.squaredLength(movement) > 0) {
|
if (vec2.squaredLength(movement) > 0) {
|
||||||
vec2.normalize(movement, movement);
|
vec2.normalize(movement, movement);
|
||||||
this.sendCommandToSubcribers(new MoveActionCommand(movement, false));
|
this.sendCommandToSubcribers(
|
||||||
|
new MoveActionCommand(movement, OptionsHandler.options.relativeMovementEnabled),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.previousPosition = position;
|
this.previousPosition = position;
|
||||||
|
|
@ -46,7 +49,12 @@ export class TouchListener extends CommandGenerator {
|
||||||
|
|
||||||
target.addEventListener('touchend', (event: TouchEvent) => {
|
target.addEventListener('touchend', (event: TouchEvent) => {
|
||||||
event.preventDefault();
|
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 { CommandReceiverSocket } from './commands/receivers/command-receiver-socket';
|
||||||
import { PlayerDecision } from './join-form-handler';
|
import { PlayerDecision } from './join-form-handler';
|
||||||
import { GameObjectContainer } from './objects/game-object-container';
|
import { GameObjectContainer } from './objects/game-object-container';
|
||||||
import { options } from './options';
|
import { OptionsHandler } from './options-handler';
|
||||||
import { BlobShape } from './shapes/blob-shape';
|
import { BlobShape } from './shapes/blob-shape';
|
||||||
import { PlanetShape } from './shapes/planet-shape';
|
import { PlanetShape } from './shapes/planet-shape';
|
||||||
|
|
||||||
|
|
@ -48,7 +48,6 @@ export class Game {
|
||||||
private readonly canvas: HTMLCanvasElement,
|
private readonly canvas: HTMLCanvasElement,
|
||||||
private readonly overlay: HTMLElement,
|
private readonly overlay: HTMLElement,
|
||||||
) {
|
) {
|
||||||
this.start();
|
|
||||||
const progressBar = document.createElement('div');
|
const progressBar = document.createElement('div');
|
||||||
progressBar.className = 'planet-progress';
|
progressBar.className = 'planet-progress';
|
||||||
overlay.appendChild(progressBar);
|
overlay.appendChild(progressBar);
|
||||||
|
|
@ -71,7 +70,7 @@ export class Game {
|
||||||
const command = deserialize(serialized);
|
const command = deserialize(serialized);
|
||||||
if (command instanceof PlayerDiedCommand) {
|
if (command instanceof PlayerDiedCommand) {
|
||||||
this.deadTimeout = command.timeout;
|
this.deadTimeout = command.timeout;
|
||||||
if (options.vibrationEnabled) {
|
if (OptionsHandler.options.vibrationEnabled) {
|
||||||
navigator.vibrate(150);
|
navigator.vibrate(150);
|
||||||
}
|
}
|
||||||
this.overlay.appendChild(this.announcmentText);
|
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);
|
const noiseTexture = await renderNoise([256, 256], 2, 1);
|
||||||
this.setupCommunication(this.playerDecision.server);
|
this.setupCommunication(this.playerDecision.server);
|
||||||
runAnimation(
|
await runAnimation(
|
||||||
this.canvas,
|
this.canvas,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
@ -162,6 +161,8 @@ export class Game {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
this.socket.close();
|
||||||
|
this.overlay.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public displayToWorldCoordinates(p: vec2): vec2 {
|
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 announcmentText = document.createElement('h2');
|
||||||
private gameLoop(
|
private gameLoop(
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
|
|
@ -196,6 +202,6 @@ export class Game {
|
||||||
this.gameObjects.stepObjects(deltaTime);
|
this.gameObjects.stepObjects(deltaTime);
|
||||||
this.gameObjects.drawObjects(this.renderer, this.overlay);
|
this.gameObjects.drawObjects(this.renderer, this.overlay);
|
||||||
|
|
||||||
return true;
|
return this.isActive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
CharacterTeam,
|
CharacterTeam,
|
||||||
settings,
|
settings,
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
import { options } from '../options';
|
import { OptionsHandler } from '../options-handler';
|
||||||
|
|
||||||
import { BlobShape } from '../shapes/blob-shape';
|
import { BlobShape } from '../shapes/blob-shape';
|
||||||
import { ViewObject } from './view-object';
|
import { ViewObject } from './view-object';
|
||||||
|
|
@ -50,7 +50,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
||||||
this.healthElement.style.width = this.health + '%';
|
this.healthElement.style.width = this.health + '%';
|
||||||
if (this.previousHealth > this.health) {
|
if (this.previousHealth > this.health) {
|
||||||
this.previousHealth = this.health;
|
this.previousHealth = this.health;
|
||||||
if (options.vibrationEnabled) {
|
if (OptionsHandler.options.vibrationEnabled) {
|
||||||
navigator.vibrate(50);
|
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;
|
padding: $small-padding;
|
||||||
border: $border;
|
border: $border;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: $border-width-focused - $border-width;
|
margin: $border-width-focused - $border-width $small-padding / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|
@ -107,27 +107,8 @@ form {
|
||||||
&:checked + label {
|
&:checked + label {
|
||||||
border-color: $accent;
|
border-color: $accent;
|
||||||
border-width: $border-width-focused;
|
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 './vars.scss';
|
||||||
|
@import './button.scss';
|
||||||
@import './form.scss';
|
@import './form.scss';
|
||||||
@import './mixins.scss';
|
@import './mixins.scss';
|
||||||
|
@import './settings.scss';
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@300&family=Open+Sans&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@300&family=Open+Sans&display=swap');
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
@ -22,8 +25,11 @@ html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
img {
|
||||||
h2 {
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
&,
|
&,
|
||||||
* {
|
* {
|
||||||
font-family: 'Comfortaa', sans-serif;
|
font-family: 'Comfortaa', sans-serif;
|
||||||
|
|
@ -31,6 +37,10 @@ h2 {
|
||||||
font-size: 6rem;
|
font-size: 6rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: $medium-padding;
|
padding-bottom: $medium-padding;
|
||||||
|
|
||||||
|
@media (max-height: $height-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
|
@ -131,30 +141,29 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#open-settings {
|
#server-container {
|
||||||
position: absolute;
|
max-height: 30vh;
|
||||||
top: 0;
|
overflow-y: auto;
|
||||||
right: 0;
|
|
||||||
|
|
||||||
animation: spin 32s linear infinite;
|
&::-webkit-scrollbar-track,
|
||||||
@keyframes spin {
|
&::-webkit-scrollbar {
|
||||||
100% {
|
background-color: transparent;
|
||||||
transform: rotate(360deg);
|
width: 2px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: $accent;
|
||||||
|
border-radius: $border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
&.scroll {
|
||||||
box-sizing: content-box;
|
box-shadow: inset 0 -8px 8px -8px rgba(0, 0, 0, 0.4),
|
||||||
|
inset 0 8px 8px -8px rgba(0, 0, 0, 0.4);
|
||||||
user-select: none;
|
&.top {
|
||||||
cursor: pointer;
|
box-shadow: inset 0 -8px 8px -8px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
padding: $medium-padding;
|
&.bottom {
|
||||||
@include square(48px);
|
box-shadow: inset 0 8px 8px -8px rgba(0, 0, 0, 0.4);
|
||||||
@media (max-width: $breakpoint) {
|
}
|
||||||
@include square(32px);
|
|
||||||
padding: $small-padding;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,17 +171,21 @@ body {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 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 {
|
&:not(:first-child) {
|
||||||
@include background;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
padding: $large-padding;
|
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,6 @@
|
||||||
@mixin background {
|
@mixin background {
|
||||||
backdrop-filter: blur(24px);
|
backdrop-filter: blur(24px);
|
||||||
@supports not (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;
|
$medium-padding: 24px;
|
||||||
$large-padding: 64px;
|
$large-padding: 64px;
|
||||||
$border-radius: 15px;
|
$border-radius: 15px;
|
||||||
$animation-time: 200ms;
|
$animation-time: 200ms ease;
|
||||||
$breakpoint: 700px;
|
$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