mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
fix sync
This commit is contained in:
parent
8f9eeadb74
commit
b08267a08c
10 changed files with 318 additions and 304 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -953,7 +953,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "demostf-frontend"
|
name = "demostf-frontend"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-session",
|
"async-session",
|
||||||
"axum",
|
"axum",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "demostf-frontend"
|
name = "demostf-frontend"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
30
flake.lock
generated
30
flake.lock
generated
|
|
@ -22,17 +22,14 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731933812,
|
"lastModified": 1732198790,
|
||||||
"narHash": "sha256-PeuRDDq1DcHxbOmXWb3nWMp5PqWUn5PSKimNbUzWEaQ=",
|
"narHash": "sha256-VNTFKcX52PRh3I88ofYTyBWCtqVQB3fVIMO64HHs3+0=",
|
||||||
"owner": "nix-community",
|
"path": "/home/robin/Projects/flakelight",
|
||||||
"repo": "flakelight",
|
"type": "path"
|
||||||
"rev": "f4b26f683be5c9ac1ec05f968dbbcb35f4bb6346",
|
|
||||||
"type": "github"
|
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"path": "/home/robin/Projects/flakelight",
|
||||||
"repo": "flakelight",
|
"type": "path"
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mill-scale": {
|
"mill-scale": {
|
||||||
|
|
@ -44,17 +41,14 @@
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1732197065,
|
"lastModified": 1732212005,
|
||||||
"narHash": "sha256-Lyosd/rJHFp1xnWPNhukQkW1hFtIiIiVDxQ+fcVypgI=",
|
"narHash": "sha256-t2+yKoxZe2JkvyHkJAAyyLS6N8yzyx0o95DuzHqv7JA=",
|
||||||
"owner": "icewind1991",
|
"path": "/home/robin/Projects/mill-scale",
|
||||||
"repo": "mill-scale",
|
"type": "path"
|
||||||
"rev": "8051d162308a80dde168b7efe012bfa1363be4ba",
|
|
||||||
"type": "github"
|
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "icewind1991",
|
"path": "/home/robin/Projects/mill-scale",
|
||||||
"repo": "mill-scale",
|
"type": "path"
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "nixpkgs/nixos-24.05";
|
nixpkgs.url = "nixpkgs/nixos-24.05";
|
||||||
flakelight = {
|
flakelight = {
|
||||||
url = "github:nix-community/flakelight";
|
# url = "github:nix-community/flakelight";
|
||||||
|
url = "path:/home/robin/Projects/flakelight";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
mill-scale = {
|
mill-scale = {
|
||||||
url = "github:icewind1991/mill-scale";
|
# url = "github:icewind1991/mill-scale";
|
||||||
|
url = "path:/home/robin/Projects/mill-scale";
|
||||||
inputs.flakelight.follows = "flakelight";
|
inputs.flakelight.follows = "flakelight";
|
||||||
};
|
};
|
||||||
npmlock2nix = {
|
npmlock2nix = {
|
||||||
|
|
@ -37,6 +39,8 @@
|
||||||
];
|
];
|
||||||
toolchain = pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
toolchain = pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||||
tools = pkgs: with pkgs; [
|
tools = pkgs: with pkgs; [
|
||||||
|
bacon
|
||||||
|
cargo-edit
|
||||||
nodejs
|
nodejs
|
||||||
nodePackages.svgo
|
nodePackages.svgo
|
||||||
typescript
|
typescript
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,29 @@
|
||||||
export interface AnalyseMenuProps {
|
export interface AnalyseMenuProps {
|
||||||
sessionName: string;
|
sessionName: string;
|
||||||
onShare: Function;
|
onShare: Function;
|
||||||
canShare: boolean;
|
canShare: boolean;
|
||||||
isShared: boolean;
|
isShared: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AnalyseMenu({sessionName, onShare, canShare, isShared}:AnalyseMenuProps) {
|
export function AnalyseMenu(props: AnalyseMenuProps) {
|
||||||
|
const loc = () => window.location.toString().replace(/\#.*/, '') + '#' + props.sessionName;
|
||||||
|
const shareText = () => (props.isShared) ?
|
||||||
|
<input class="share-text" value={loc()} readOnly={true}
|
||||||
|
title="Use this link to join the current session"
|
||||||
|
style={{width: `${(loc().length * 8)}px`}}
|
||||||
|
onFocus={(event) => {
|
||||||
|
(event.target as HTMLInputElement).select()
|
||||||
|
}}/> : '';
|
||||||
|
|
||||||
const loc = window.location.toString().replace(/\#.+/, '') + '#' + sessionName;
|
const shareButton = () => (props.canShare) ? <div class="analyse-menu">
|
||||||
const shareText = (isShared) ?
|
<button class="share-session" title="Start a shared session"
|
||||||
<input class="share-text" value={loc} readOnly={true}
|
onClick={() => {
|
||||||
title="Use this link to join the current session"
|
props.onShare()
|
||||||
style={{width: `${(loc.length*8)}px`}}
|
}}/>
|
||||||
onFocus={(event)=>{(event.target as HTMLInputElement).select()}}/> : '';
|
{shareText}
|
||||||
|
</div> : '';
|
||||||
|
|
||||||
const shareButton = (canShare) ? <div class="analyse-menu">
|
return (<div>
|
||||||
<button class="share-session" title="Start a shared session"
|
{shareButton}
|
||||||
onClick={()=>{onShare()}}/>
|
</div>)
|
||||||
{shareText}
|
|
||||||
</div>: '';
|
|
||||||
|
|
||||||
return (<div>
|
|
||||||
{shareButton}
|
|
||||||
</div>)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,178 +12,182 @@ import {Session, StateUpdate} from "./Session";
|
||||||
import {DemoHead} from "../../header";
|
import {DemoHead} from "../../header";
|
||||||
|
|
||||||
export interface AnalyseProps {
|
export interface AnalyseProps {
|
||||||
header: DemoHead;
|
header: DemoHead;
|
||||||
isStored: boolean;
|
isStored: boolean;
|
||||||
parser: AsyncParser;
|
parser: AsyncParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Analyser = (props: AnalyseProps) => {
|
export const Analyser = (props: AnalyseProps) => {
|
||||||
const parser = props.parser;
|
const parser = props.parser;
|
||||||
const intervalPerTick = props.header.duration / props.header.ticks;
|
const intervalPerTick = props.header.duration / props.header.ticks;
|
||||||
|
|
||||||
const [tick, setTick] = createSignal<number>(0);
|
const [tick, setTick] = createSignal<number>(0);
|
||||||
const [scale, setScale] = createSignal<number>(1);
|
const [scale, setScale] = createSignal<number>(1);
|
||||||
const [playing, setPlaying] = createSignal<boolean>(false);
|
const [playing, setPlaying] = createSignal<boolean>(false);
|
||||||
const [sessionName, setSessionName] = createSignal<string>("");
|
const [sessionName, setSessionName] = createSignal<string>("");
|
||||||
|
|
||||||
let lastFrameTime = 0;
|
let lastFrameTime = 0;
|
||||||
let playStartTick = 0;
|
let playStartTick = 0;
|
||||||
let playStartTime = 0;
|
let playStartTime = 0;
|
||||||
|
|
||||||
const onUpdate = (update: StateUpdate) => {
|
const onUpdate = (update: StateUpdate) => {
|
||||||
if (update["tick"]) {
|
if (update["tick"]) {
|
||||||
setTick(update["tick"]);
|
setTick(update["tick"]);
|
||||||
}
|
}
|
||||||
if (update["playing"]) {
|
if (update.hasOwnProperty("playing")) {
|
||||||
setPlaying(update["playing"]);
|
if (update["playing"]) {
|
||||||
}
|
play();
|
||||||
}
|
} else {
|
||||||
|
pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let session: Session | null = null;
|
let session: Session | null = null;
|
||||||
if (props.isStored && window.location.hash) {
|
if (props.isStored && window.location.hash) {
|
||||||
const parsed = parseInt(window.location.hash.substr(1), 10);
|
const parsed = parseInt(window.location.hash.substr(1), 10);
|
||||||
if (('#' + parsed) === window.location.hash) {
|
if (('#' + parsed) === window.location.hash) {
|
||||||
setTick(Math.floor(parsed));
|
setTick(Math.floor(parsed));
|
||||||
} else {
|
} else {
|
||||||
const name = window.location.hash.substring(1);
|
const name = window.location.hash.substring(1);
|
||||||
Session.join(name, onUpdate);
|
session = Session.join(name, onUpdate);
|
||||||
setSessionName(name);
|
setSessionName(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = parser.demo.header.map;
|
const map = parser.demo.header.map;
|
||||||
const backgroundBoundaries = getMapBoundaries(map);
|
const backgroundBoundaries = getMapBoundaries(map);
|
||||||
if (!backgroundBoundaries) {
|
if (!backgroundBoundaries) {
|
||||||
throw new Error(`Map not supported "${map}".`);
|
throw new Error(`Map not supported "${map}".`);
|
||||||
}
|
}
|
||||||
const worldSize = {
|
const worldSize = {
|
||||||
width: backgroundBoundaries.boundary_max.x - backgroundBoundaries.boundary_min.x,
|
width: backgroundBoundaries.boundary_max.x - backgroundBoundaries.boundary_min.x,
|
||||||
height: backgroundBoundaries.boundary_max.y - backgroundBoundaries.boundary_min.y,
|
height: backgroundBoundaries.boundary_max.y - backgroundBoundaries.boundary_min.y,
|
||||||
};
|
};
|
||||||
|
|
||||||
const setTickNow = (tick) => {
|
const setTickNow = (tick) => {
|
||||||
lastFrameTime = 0;
|
lastFrameTime = 0;
|
||||||
playStartTick = tick;
|
playStartTick = tick;
|
||||||
playStartTime = window.performance.now();
|
playStartTime = window.performance.now();
|
||||||
setTick(tick);
|
setTick(tick);
|
||||||
setHash(tick);
|
setHash(tick);
|
||||||
if (session) {
|
if (session) {
|
||||||
session.update({tick});
|
session.update({tick});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
setPlaying(false);
|
setPlaying(false);
|
||||||
lastFrameTime = 0;
|
lastFrameTime = 0;
|
||||||
if (session) {
|
if (session) {
|
||||||
session.update({playing: false});
|
session.update({playing: false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const play = () => {
|
const play = () => {
|
||||||
playStartTick = tick();
|
playStartTick = tick();
|
||||||
playStartTime = window.performance.now();
|
playStartTime = window.performance.now();
|
||||||
setPlaying(true);
|
setPlaying(true);
|
||||||
requestAnimationFrame(animFrame);
|
requestAnimationFrame(animFrame);
|
||||||
if (session) {
|
if (session) {
|
||||||
session.update({playing: false});
|
session.update({playing: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const togglePlay = () => {
|
const togglePlay = () => {
|
||||||
if (playing()) {
|
if (playing()) {
|
||||||
pause();
|
pause();
|
||||||
} else {
|
} else {
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncPlayTick = debounce(500, () => {
|
const syncPlayTick = throttle(2500, () => {
|
||||||
if (session) {
|
if (session) {
|
||||||
session.update({
|
session.update({
|
||||||
playing: playing(),
|
playing: playing(),
|
||||||
tick: tick(),
|
tick: tick(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const setHash = debounce(250, (tick) => {
|
const setHash = debounce(250, (tick) => {
|
||||||
if (!session && props.isStored) {
|
if (!session && props.isStored) {
|
||||||
history.replaceState('', '', '#' + tick);
|
history.replaceState('', '', '#' + tick);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const animFrame = (timestamp:number) => {
|
const animFrame = (timestamp: number) => {
|
||||||
const timePassed = (timestamp - playStartTime) / 1000;
|
const timePassed = (timestamp - playStartTime) / 1000;
|
||||||
const targetTick = playStartTick + (Math.round(timePassed / intervalPerTick));
|
const targetTick = playStartTick + (Math.round(timePassed / intervalPerTick));
|
||||||
lastFrameTime = timestamp;
|
lastFrameTime = timestamp;
|
||||||
if (targetTick >= (parser.demo.tick - 1)) {
|
if (targetTick >= (parser.demo.tick - 1)) {
|
||||||
pause();
|
pause();
|
||||||
}
|
}
|
||||||
setHash(targetTick);
|
setHash(targetTick);
|
||||||
setTick(targetTick);
|
setTick(targetTick);
|
||||||
syncPlayTick();
|
syncPlayTick();
|
||||||
|
|
||||||
if (playing()) {
|
if (playing()) {
|
||||||
requestAnimationFrame(animFrame);
|
requestAnimationFrame(animFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const players = () => parser.getPlayersAtTick(tick());
|
const players = () => parser.getPlayersAtTick(tick());
|
||||||
const buildings = () => parser.getBuildingsAtTick(tick());
|
const buildings = () => parser.getBuildingsAtTick(tick());
|
||||||
const kills = parser.getKills();
|
const kills = parser.getKills();
|
||||||
const playButtonText = () => (playing()) ? '⏸' : '▶️';
|
const playButtonText = () => (playing()) ? '⏸' : '▶️';
|
||||||
const disabled = session && !session.isOwner();
|
const disabled = session && !session.isOwner();
|
||||||
const isShared = () => sessionName() !== '';
|
const isShared = () => sessionName() !== '';
|
||||||
|
|
||||||
console.log(intervalPerTick);
|
console.log(intervalPerTick);
|
||||||
|
|
||||||
const timeTitle = () => `${tickToTime(tick(), intervalPerTick)} (tick ${tick()})`;
|
const timeTitle = () => `${tickToTime(tick(), intervalPerTick)} (tick ${tick()})`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div class="map-holder">
|
<div class="map-holder">
|
||||||
<MapContainer contentSize={worldSize}
|
<MapContainer contentSize={worldSize}
|
||||||
onScale={setScale}>
|
onScale={setScale}>
|
||||||
<MapRender size={worldSize}
|
<MapRender size={worldSize}
|
||||||
players={players()}
|
players={players()}
|
||||||
buildings={buildings()}
|
buildings={buildings()}
|
||||||
header={props.header}
|
header={props.header}
|
||||||
world={backgroundBoundaries}
|
world={backgroundBoundaries}
|
||||||
scale={scale()}/>
|
scale={scale()}/>
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
<AnalyseMenu sessionName={sessionName()}
|
<AnalyseMenu sessionName={sessionName()}
|
||||||
onShare={() => {
|
onShare={() => {
|
||||||
session = Session.create({
|
session = Session.create({
|
||||||
tick: tick(),
|
tick: tick(),
|
||||||
playing: playing()
|
playing: playing()
|
||||||
});
|
});
|
||||||
setSessionName(session.sessionName);
|
setSessionName(session.sessionName);
|
||||||
}}
|
}}
|
||||||
canShare={props.isStored && !disabled}
|
canShare={props.isStored && !disabled}
|
||||||
isShared={isShared()}
|
isShared={isShared()}
|
||||||
/>
|
/>
|
||||||
<SpecHUD parser={parser} tick={tick()}
|
<SpecHUD parser={parser} tick={tick()}
|
||||||
players={players()} kills={kills}/>
|
players={players()} kills={kills}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="time-control"
|
<div class="time-control"
|
||||||
title={timeTitle()}>
|
title={timeTitle()}>
|
||||||
<input class="play-pause-button" type="button"
|
<input class="play-pause-button" type="button"
|
||||||
value={playButtonText()}
|
value={playButtonText()}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={togglePlay}
|
onClick={togglePlay}
|
||||||
/>
|
/>
|
||||||
<Timeline parser={parser} tick={tick()}
|
<Timeline parser={parser} tick={tick()}
|
||||||
onSetTick={throttle(50, (tick) => {
|
onSetTick={throttle(50, (tick) => {
|
||||||
setTickNow(tick);
|
setTickNow(tick);
|
||||||
})}
|
})}
|
||||||
disabled={disabled}/>
|
disabled={disabled}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function tickToTime(tick: number, intervalPerTick: number): string {
|
function tickToTime(tick: number, intervalPerTick: number): string {
|
||||||
let seconds = Math.floor(tick * intervalPerTick);
|
let seconds = Math.floor(tick * intervalPerTick);
|
||||||
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`;
|
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export class Session {
|
||||||
this.sessionName = name;
|
this.sessionName = name;
|
||||||
this.initialState = initialState;
|
this.initialState = initialState;
|
||||||
this.onState = onState;
|
this.onState = onState;
|
||||||
|
this.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static create(state: PlaybackState): Session {
|
public static create(state: PlaybackState): Session {
|
||||||
|
|
@ -98,7 +99,7 @@ export class Session {
|
||||||
tick: update["tick"]
|
tick: update["tick"]
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (update["playing"]) {
|
if (update.hasOwnProperty("playing")) {
|
||||||
this.socket.send(JSON.stringify({
|
this.socket.send(JSON.stringify({
|
||||||
type: 'play',
|
type: 'play',
|
||||||
session: this.sessionName,
|
session: this.sessionName,
|
||||||
|
|
|
||||||
|
|
@ -293,10 +293,10 @@ impl Render for DemoFormat {
|
||||||
MapMode::Ultiduo => "Ultiduo",
|
MapMode::Ultiduo => "Ultiduo",
|
||||||
MapMode::Bball => "BBall",
|
MapMode::Bball => "BBall",
|
||||||
MapMode::Other => match self.player_count {
|
MapMode::Other => match self.player_count {
|
||||||
17 | 18 | 19 => "HL",
|
17..=19 => "HL",
|
||||||
15 | 14 => "Prolander",
|
14..=15 => "Prolander",
|
||||||
13 | 12 | 11 => "6v6",
|
11..=13 => "6v6",
|
||||||
7 | 8 | 9 => "4v4",
|
7..=9 => "4v4",
|
||||||
_ => "Other",
|
_ => "Other",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,42 @@
|
||||||
.analyse-menu {
|
.analyse-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
|
||||||
|
|
||||||
& .share-session {
|
|
||||||
|
|
||||||
background: transparent;
|
|
||||||
color: var(--primary-color);
|
|
||||||
font-size: 200%;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin: 10px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
background-image: url("images/link_white.svg");
|
|
||||||
background-size: contain;
|
|
||||||
|
|
||||||
&:active, &:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
& .share-text {
|
& .share-session {
|
||||||
color: var(--primary-color);
|
|
||||||
background-color: var(--text-secondary);
|
|
||||||
padding: 5px;
|
|
||||||
margin-top: 0;
|
|
||||||
border: 1px #888 solid;
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
&:active, &:focus {
|
background: transparent;
|
||||||
outline: none;
|
color: var(--primary-color);
|
||||||
|
font-size: 200%;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin: 10px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background-image: url("inline://images//link_white.svg");
|
||||||
|
background-size: contain;
|
||||||
|
|
||||||
|
&:active, &:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .share-text {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background-color: var(--text-secondary);
|
||||||
|
padding: 5px;
|
||||||
|
margin-top: 0;
|
||||||
|
border: 1px #888 solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
&:active, &:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,83 @@
|
||||||
.map-holder {
|
.map-holder {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 32px;
|
top: 32px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 32px - 100px);
|
height: calc(100% - 32px - 100px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-control {
|
.time-control {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100px;
|
|
||||||
background-color: var(--primary-color-accent);
|
|
||||||
|
|
||||||
& .timeline {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 64px;
|
|
||||||
width: calc(100% - 64px);
|
|
||||||
}
|
|
||||||
|
|
||||||
& .play-pause-button {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
width: 64px;
|
background-color: var(--primary-color-accent);
|
||||||
background-color: transparent;
|
|
||||||
color: black;
|
|
||||||
font-size: 200%;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&:focus, &:active {
|
& .timeline {
|
||||||
outline: none;
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 64px;
|
||||||
|
width: calc(100% - 64px);
|
||||||
|
|
||||||
|
& .timeline-progress[disabled] {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .play-pause-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100px;
|
||||||
|
width: 64px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: black;
|
||||||
|
font-size: 200%;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:focus, &:active {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-holder {
|
.error-holder {
|
||||||
.error-image {
|
.error-image {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
font-size: 250%;
|
font-size: 250%;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
display: block;
|
display: block;
|
||||||
content: '';
|
content: '';
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 50% 50%;
|
background-position: 50% 50%;
|
||||||
background-image: url('images/teleporter.png');
|
background-image: url('images/teleporter.png');
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
margin: 50px 0;
|
margin: 50px 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
& .error {
|
& .error {
|
||||||
background-color: #FF9494;
|
background-color: #FF9494;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
margin: 0 -30px;
|
margin: 0 -30px;
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
padding-left: 74px;
|
padding-left: 74px;
|
||||||
background-image: url('images/error.png');
|
background-image: url('images/error.png');
|
||||||
background-size: 32px;
|
background-size: 32px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 32px 32px;
|
background-position: 32px 32px;
|
||||||
|
|
||||||
& .error-hint {
|
& .error-hint {
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue