mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 11:23:50 +00:00
Feat/studio clock (#60)
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSocket } from 'app/context/socketContext';
|
||||
import {Editable, EditableInput, EditablePreview} from '@chakra-ui/editable';
|
||||
import {Switch} from "@chakra-ui/react";
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useSocket} from 'app/context/socketContext';
|
||||
import VisibleIconBtn from 'common/components/buttons/VisibleIconBtn';
|
||||
import style from './MessageControl.module.css';
|
||||
import style from './MessageControl.module.scss';
|
||||
|
||||
const inputProps = {
|
||||
size: 'sm',
|
||||
};
|
||||
|
||||
const InputRow = (props) => {
|
||||
const { label, placeholder, text, visible } = props;
|
||||
const {label, placeholder, text, visible} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -22,8 +23,8 @@ const InputRow = (props) => {
|
||||
className={style.inline}
|
||||
color={text === '' ? '#666' : 'inherit'}
|
||||
>
|
||||
<EditablePreview className={style.padleft} />
|
||||
<EditableInput className={style.padleft} />
|
||||
<EditablePreview className={style.padleft}/>
|
||||
<EditableInput className={style.padleft}/>
|
||||
</Editable>
|
||||
<VisibleIconBtn
|
||||
active={visible || undefined}
|
||||
@@ -49,33 +50,42 @@ export default function MessageControl() {
|
||||
text: '',
|
||||
visible: false,
|
||||
});
|
||||
const [onAir, setonAir] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (socket == null) return;
|
||||
|
||||
// Handle presenter messages
|
||||
socket.on('messages-presenter', (data) => {
|
||||
setPres({ ...data });
|
||||
setPres({...data});
|
||||
});
|
||||
|
||||
// Handle public messages
|
||||
socket.on('messages-public', (data) => {
|
||||
setPubl({ ...data });
|
||||
setPubl({...data});
|
||||
});
|
||||
|
||||
// Handle lower third messages
|
||||
socket.on('messages-lower', (data) => {
|
||||
setLower({ ...data });
|
||||
setLower({...data});
|
||||
});
|
||||
|
||||
// Handle lower third messages
|
||||
socket.on('onAir', (data) => {
|
||||
setonAir(data);
|
||||
});
|
||||
|
||||
// Ask for up to date data
|
||||
socket.emit('get-messages');
|
||||
// Ask for onAir state
|
||||
socket.emit('get-onAir');
|
||||
|
||||
// Clear listeners
|
||||
return () => {
|
||||
socket.off('messages-public');
|
||||
socket.off('messages-presenter');
|
||||
socket.off('messages-lower');
|
||||
socket.off('onAir');
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
@@ -99,38 +109,54 @@ export default function MessageControl() {
|
||||
case 'toggle-lower-visible':
|
||||
socket.emit('set-lower-visible', !lower.visible);
|
||||
break;
|
||||
|
||||
case 'toggle-onAir':
|
||||
socket.emit('set-onAir', !onAir);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.messageContainer}>
|
||||
<InputRow
|
||||
label='Presenter screen message'
|
||||
placeholder='only the presenter screens see this'
|
||||
text={pres.text}
|
||||
visible={pres.visible}
|
||||
changeHandler={(event) => messageControl('pres-text', event)}
|
||||
actionHandler={() => messageControl('toggle-pres-visible')}
|
||||
/>
|
||||
<InputRow
|
||||
label='Public screen message'
|
||||
placeholder='public screens will render this'
|
||||
text={publ.text}
|
||||
visible={publ.visible}
|
||||
changeHandler={(event) => messageControl('publ-text', event)}
|
||||
actionHandler={() => messageControl('toggle-publ-visible')}
|
||||
/>
|
||||
<InputRow
|
||||
label='Lower third message'
|
||||
placeholder='visible in lower third screen'
|
||||
text={lower.text}
|
||||
visible={lower.visible}
|
||||
changeHandler={(event) => messageControl('lower-text', event)}
|
||||
actionHandler={() => messageControl('toggle-lower-visible')}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<div className={style.messageContainer}>
|
||||
<InputRow
|
||||
label='Presenter screen message'
|
||||
placeholder='only the presenter screens see this'
|
||||
text={pres.text}
|
||||
visible={pres.visible}
|
||||
changeHandler={(event) => messageControl('pres-text', event)}
|
||||
actionHandler={() => messageControl('toggle-pres-visible')}
|
||||
/>
|
||||
<InputRow
|
||||
label='Public screen message'
|
||||
placeholder='public screens will render this'
|
||||
text={publ.text}
|
||||
visible={publ.visible}
|
||||
changeHandler={(event) => messageControl('publ-text', event)}
|
||||
actionHandler={() => messageControl('toggle-publ-visible')}
|
||||
/>
|
||||
<InputRow
|
||||
label='Lower third message'
|
||||
placeholder='visible in lower third screen'
|
||||
text={lower.text}
|
||||
visible={lower.visible}
|
||||
changeHandler={(event) => messageControl('lower-text', event)}
|
||||
actionHandler={() => messageControl('toggle-lower-visible')}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.onAirToggle}>
|
||||
<Switch
|
||||
colorScheme='green'
|
||||
size='md'
|
||||
isChecked={onAir}
|
||||
onChange={() => messageControl('toggle-onAir')}>
|
||||
On Air?
|
||||
</Switch>
|
||||
<span className={style.oscLabel}>
|
||||
{`/ontime/offAir << OSC >> /ontime/onAir`}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
.messageContainer {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.inputItems {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0.9em;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.inline {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.padleft {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
.messageContainer,
|
||||
.onAirToggle {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
padding: 0.5em;
|
||||
|
||||
}
|
||||
|
||||
.messageContainer {
|
||||
flex-direction: column;
|
||||
|
||||
.inputItems {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 1em;
|
||||
}
|
||||
.label {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0.9em;
|
||||
color: #ccc;
|
||||
}
|
||||
.inline {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.padleft {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.onAirToggle {
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
line-height: 3em;
|
||||
|
||||
.onAirLabel {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.oscLabel {
|
||||
color: #4bffabcc;
|
||||
font-size: 0.8em;
|
||||
float: right;
|
||||
padding-right: 1em;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,9 @@ const withSocket = (Component) => {
|
||||
const WrappedComponent = (props) => {
|
||||
const {
|
||||
data: eventsData,
|
||||
status: eventsDataStatus,
|
||||
isError: eventsDataIsError,
|
||||
} = useFetch(EVENTS_TABLE, fetchAllEvents);
|
||||
const {
|
||||
data: genData,
|
||||
status: genDataStatus,
|
||||
isError: genDataIsError,
|
||||
} = useFetch(EVENT_TABLE, fetchEvent);
|
||||
|
||||
const [publicEvents, setPublicEvents] = useState([]);
|
||||
@@ -58,6 +54,7 @@ const withSocket = (Component) => {
|
||||
presenterNext: '',
|
||||
});
|
||||
const [selectedId, setSelectedId] = useState(null);
|
||||
const [nextId, setNextId] = useState(null);
|
||||
const [publicSelectedId, setPublicSelectedId] = useState(null);
|
||||
const [general, setGeneral] = useState({
|
||||
title: '',
|
||||
@@ -67,6 +64,7 @@ const withSocket = (Component) => {
|
||||
endMessage: '',
|
||||
});
|
||||
const [playback, setPlayback] = useState(null);
|
||||
const [onAir, setOnAir] = useState(false);
|
||||
|
||||
// Ask for update on load
|
||||
useEffect(() => {
|
||||
@@ -96,6 +94,9 @@ const withSocket = (Component) => {
|
||||
socket.on('playstate', (data) => {
|
||||
setPlayback(data);
|
||||
});
|
||||
socket.on('onAir', (data) => {
|
||||
setOnAir(data);
|
||||
});
|
||||
|
||||
// Handle titles
|
||||
socket.on('titles', (data) => {
|
||||
@@ -112,6 +113,9 @@ const withSocket = (Component) => {
|
||||
socket.on('publicselected-id', (data) => {
|
||||
setPublicSelectedId(data);
|
||||
});
|
||||
socket.on('next-id', (data) => {
|
||||
setNextId(data);
|
||||
});
|
||||
|
||||
// Ask for up to date data
|
||||
socket.emit('get-messages');
|
||||
@@ -124,6 +128,7 @@ const withSocket = (Component) => {
|
||||
|
||||
// ask for playstate
|
||||
socket.emit('get-playstate');
|
||||
socket.emit('get-onAir')
|
||||
|
||||
// Ask for up titles
|
||||
socket.emit('get-titles');
|
||||
@@ -131,6 +136,7 @@ const withSocket = (Component) => {
|
||||
|
||||
// Ask for up selected
|
||||
socket.emit('get-selected-id');
|
||||
socket.emit('get-next-id');
|
||||
|
||||
// Clear listeners
|
||||
return () => {
|
||||
@@ -139,9 +145,11 @@ const withSocket = (Component) => {
|
||||
socket.off('messages-lower');
|
||||
socket.off('timer');
|
||||
socket.off('playstate');
|
||||
socket.off('onAir');
|
||||
socket.off('titles');
|
||||
socket.off('publictitles');
|
||||
socket.off('selected-id');
|
||||
socket.emit('next-id');
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
@@ -224,6 +232,7 @@ const withSocket = (Component) => {
|
||||
...timer,
|
||||
finished: playback === 'start' && timer.running <= 0 && timer.startedAt,
|
||||
clock: stringFromMillis(timer.clock),
|
||||
clockNoSeconds: stringFromMillis(timer.clock, false),
|
||||
playstate: playback,
|
||||
};
|
||||
|
||||
@@ -240,7 +249,9 @@ const withSocket = (Component) => {
|
||||
backstageEvents={backstageEvents}
|
||||
selectedId={selectedId}
|
||||
publicSelectedId={publicSelectedId}
|
||||
nextId={nextId}
|
||||
general={general}
|
||||
onAir={onAir}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import NavLogo from 'common/components/nav/NavLogo';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import TitleSide from 'common/components/views/TitleSide';
|
||||
import {getEventsWithDelay} from "../../../common/utils/eventsManager";
|
||||
|
||||
export default function StageManager(props) {
|
||||
const { publ, title, time, backstageEvents, selectedId, general } = props;
|
||||
@@ -20,23 +21,9 @@ export default function StageManager(props) {
|
||||
useEffect(() => {
|
||||
if (backstageEvents == null) return;
|
||||
|
||||
let events = [...backstageEvents];
|
||||
setFilteredEvents(getEventsWithDelay(backstageEvents));
|
||||
|
||||
// Add running delay
|
||||
let delay = 0;
|
||||
for (const e of events) {
|
||||
if (e.type === 'block') delay = 0;
|
||||
else if (e.type === 'delay') delay = delay + e.duration;
|
||||
else if (e.type === 'event' && delay > 0) {
|
||||
e.timeStart += delay;
|
||||
e.timeEnd += delay;
|
||||
}
|
||||
}
|
||||
|
||||
// filter just events
|
||||
let filtered = events.filter((e) => e.type === 'event');
|
||||
|
||||
setFilteredEvents(filtered);
|
||||
console.log('hhh', getEventsWithDelay(backstageEvents))
|
||||
}, [backstageEvents]);
|
||||
|
||||
// Format messages
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function PresenterView(props) {
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Speaker Screen';
|
||||
document.title = 'ontime - Presenter Screen';
|
||||
}, []);
|
||||
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useEffect, useState } from 'react';
|
||||
import style from './LowerClean.module.css';
|
||||
import NavLogo from "../../../../common/components/nav/NavLogo";
|
||||
|
||||
export default function LowerClean(props) {
|
||||
const { lower, title, options } = props;
|
||||
@@ -86,6 +87,9 @@ export default function LowerClean(props) {
|
||||
fontSize: `${sizeMultiplier}vh`,
|
||||
}}
|
||||
>
|
||||
|
||||
<NavLogo isHidden />
|
||||
|
||||
<AnimatePresence>
|
||||
{showLower && (
|
||||
<motion.div
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useEffect, useState } from 'react';
|
||||
import style from './LowerLines.module.css';
|
||||
import NavLogo from "../../../../common/components/nav/NavLogo";
|
||||
|
||||
export default function LowerLines(props) {
|
||||
const { lower, title, options } = props;
|
||||
@@ -126,6 +127,9 @@ export default function LowerLines(props) {
|
||||
fontSize: `${sizeMultiplier}vh`,
|
||||
}}
|
||||
>
|
||||
|
||||
<NavLogo isHidden />
|
||||
|
||||
<AnimatePresence>
|
||||
{showLower && (
|
||||
<motion.div
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import style from "./StudioClock.module.scss";
|
||||
import useFitText from "use-fit-text";
|
||||
import NavLogo from "../../../common/components/nav/NavLogo";
|
||||
import {useEffect, useState} from "react";
|
||||
import {formatDisplay} from "../../../common/utils/dateConfig";
|
||||
import {formatEventList, trimEventlist} from "../../../common/utils/eventsManager";
|
||||
|
||||
export default function StudioClock(props) {
|
||||
const { title, time, backstageEvents, selectedId, nextId, onAir } = props;
|
||||
const { fontSize, ref } = useFitText({maxFontSize:500});
|
||||
const [hoursNow, minutesNow] = time.clockNoSeconds.split(':');
|
||||
const [schedule, setSchedule] = useState([]);
|
||||
|
||||
const hoursIndicators = [...Array(12).keys()];
|
||||
const minutesIndicators = [...Array(60).keys()];
|
||||
const MAX_TITLES = 8;
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Studio Clock';
|
||||
}, []);
|
||||
|
||||
// Prepare event list
|
||||
// Todo: useMemo()
|
||||
useEffect(() => {
|
||||
if (backstageEvents == null) return;
|
||||
|
||||
const events = backstageEvents.filter((e) => e.type === 'event');
|
||||
|
||||
let e = trimEventlist(events, selectedId, MAX_TITLES);
|
||||
e = formatEventList(e, selectedId, nextId);
|
||||
setSchedule(e);
|
||||
|
||||
}, [backstageEvents, selectedId, nextId]);
|
||||
|
||||
return (
|
||||
<div className={style.container}>
|
||||
<NavLogo />
|
||||
<div className={style.clockContainer}>
|
||||
<div className={style.time}>{time.clockNoSeconds}</div>
|
||||
<div
|
||||
ref={ref}
|
||||
className={style.nextTitle}
|
||||
style={{fontSize, height:'100px', width:'100%', maxWidth:'680px'}}
|
||||
>
|
||||
{title.titleNext}
|
||||
</div>
|
||||
<div className={time.running > 0 ? style.nextCountdown: style.nextCountdown__overtime}>
|
||||
{selectedId != null && formatDisplay(time.running)}
|
||||
</div>
|
||||
<div className={style.indicators}>
|
||||
{hoursIndicators.map(i => (
|
||||
<div
|
||||
key = {i}
|
||||
className={i <= hoursNow ? style.hours__active : style.hours}
|
||||
style={{
|
||||
transform: `rotate(${360/12*i-90}deg) translateX(380px)`
|
||||
}}/>
|
||||
)
|
||||
)}
|
||||
{minutesIndicators.map(i => (
|
||||
<div
|
||||
key={i}
|
||||
className={i <= minutesNow ? style.min__active : style.min}
|
||||
style={{
|
||||
transform: `rotate(${360/60*i-90}deg) translateX(415px)`
|
||||
}}/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.scheduleContainer}>
|
||||
<div className={onAir ? style.onAir : style.onAir__idle}>ON AIR</div>
|
||||
<div className={style.schedule}>
|
||||
<ul>
|
||||
{schedule.map((s) => (
|
||||
<li key={s.id} className={s.isNow ? style.now : s.isNext ? style.next : ''}>
|
||||
<div className={s.isNow ? style.decorator__active : s.isNext ? style.decorator__next : style.decorator}/>{`${s.time} ${s.title}`}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
@font-face {
|
||||
font-family: "digital-clock";
|
||||
src: local('digital-7'), url('./../../../assets/fonts/digital-7.mono.ttf') format('truetype') ;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
box-sizing: border-box; /* reset */
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
height: 100vh;
|
||||
padding: 1vw;
|
||||
|
||||
background: #111;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1000px 1fr;
|
||||
gap: 50px;
|
||||
grid-template-areas: "clck schd";
|
||||
|
||||
/* =============== CLOCK STUFF ==================*/
|
||||
|
||||
$clock-size: 900px;
|
||||
$size-hours: 20px;
|
||||
$half-hours: 10px;
|
||||
$size-min: 18px;
|
||||
$half-min: 9px;
|
||||
$red-active: #c53030;
|
||||
$red-idle: darken($red-active, 30%);
|
||||
$cyan-active: #0ff;
|
||||
$cyan-idle: #0aa;
|
||||
|
||||
.clockContainer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
grid-area: clck;
|
||||
width: $clock-size;
|
||||
height: $clock-size;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
font-family: digital-clock, monospace;
|
||||
text-transform: uppercase;
|
||||
|
||||
|
||||
.time {
|
||||
margin-top: 175px;
|
||||
color: $red-active;
|
||||
font-size: 275px;
|
||||
line-height: 0.8em;
|
||||
text-shadow: rgb(180,0,0) 0 0 20px;
|
||||
}
|
||||
.nextTitle:after,
|
||||
.nextCountdown:after,
|
||||
.nextCountdown__overtime:after {
|
||||
content: '\200b';
|
||||
}
|
||||
.nextTitle {
|
||||
color:$cyan-idle;
|
||||
line-height: 100px;
|
||||
}
|
||||
.nextCountdown,
|
||||
.nextCountdown__overtime {
|
||||
font-size: 100px;
|
||||
line-height: 1em;
|
||||
}
|
||||
.nextCountdown {
|
||||
color: $cyan-active;
|
||||
text-shadow: rgb(0,100,100) 0 0 20px;
|
||||
}
|
||||
.nextCountdown::before {
|
||||
content: '-';
|
||||
}
|
||||
.nextCountdown__overtime {
|
||||
color: darken($red-active, 10%);
|
||||
}
|
||||
.indicators {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.min,
|
||||
.min__active,
|
||||
.hours,
|
||||
.hours__active {
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
background: $red-idle;
|
||||
}
|
||||
.min,
|
||||
.min__active {
|
||||
min-height: $size-min;
|
||||
width: $size-min;
|
||||
top: calc(50% - #{$half_min});
|
||||
left: calc(50% - #{$half_min});
|
||||
}
|
||||
.hours,
|
||||
.hours__active{
|
||||
min-height:$size-hours;
|
||||
width: $size-hours;
|
||||
top: calc(50% - #{$half_hours});
|
||||
left: calc(50% - #{$half_hours});
|
||||
}
|
||||
.min__active,
|
||||
.hours__active {
|
||||
background: $red-active;
|
||||
box-shadow: 0 0 10px 2px rgba(255,0,0,0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============= SCHEDULE STUFF =================*/
|
||||
|
||||
.scheduleContainer {
|
||||
grid-area: schd;
|
||||
margin: 50px 0;
|
||||
padding-right: 50px;
|
||||
font-family: digital-clock, monospace;
|
||||
text-transform: uppercase;
|
||||
|
||||
|
||||
.onAir,
|
||||
.onAir__idle{
|
||||
padding-bottom: 50px;
|
||||
font-size: 190px;
|
||||
line-height: 0.9em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.onAir {
|
||||
color: $red-active;
|
||||
text-shadow: rgb(150,0,0) 0 0 20px;
|
||||
}
|
||||
.onAir__idle {
|
||||
color: $red-idle;
|
||||
}
|
||||
.schedule {
|
||||
|
||||
ul {
|
||||
color: $cyan-idle;
|
||||
line-height: 1em;
|
||||
list-style: none;
|
||||
font-size: 40px;
|
||||
}
|
||||
li {
|
||||
padding-bottom: 0.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
.now {
|
||||
color: $cyan-active;
|
||||
text-shadow: rgb(0,100,100) 0 0 20px;
|
||||
}
|
||||
.next {
|
||||
color: $red-active;
|
||||
text-shadow: rgb(100,0,0) 0 0 20px;
|
||||
}
|
||||
.decorator,
|
||||
.decorator__active,
|
||||
.decorator__next {
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: $red-idle;
|
||||
min-height: $size-hours;
|
||||
max-height: $size-hours;
|
||||
width: $size-hours;
|
||||
}
|
||||
.decorator__active {
|
||||
background: $cyan-active;
|
||||
box-shadow: 0 0 10px 2px rgba(0,255,255,0.25);
|
||||
}
|
||||
.decorator__next{
|
||||
background: $red-active;
|
||||
box-shadow: 0 0 10px 2px rgba(255,0,0,0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1600px) {
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-areas: "clck";
|
||||
grid-template-columns: 100%;
|
||||
place-content: center;
|
||||
}
|
||||
.scheduleContainer{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user