Files
Studio/Studio Dashboard V2.html
2026-01-18 11:57:54 +00:00

442 lines
11 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>Studio Dashboard</title>
<style>
body {
background-color: black;
}
.wrapper {
display: flex;
margin: 0 2.5%;
justify-content: space-between;
}
.left-column {
flex-basis: 80%;
}
.left-column svg {
max-width: 100vw;
max-height: 98vh;
width: auto;
height: auto;
display: block;
margin: 0 auto;
font-family: dotmatrix;
display: block;
}
.right-column {
flex-basis: 20%;
}
.hidden {
display: none;
}
.content {
max-width: 100%;
}
#settings-button {
font-family: play, sans-serif;
background-color: #222;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 5px;
font-size: 16px;
text-transform: uppercase;
cursor: pointer;
}
#settings-button:hover {
background-color: #444;
}
.mic {
font-family: play, sans-serif;
font-size: 2vw;
}
#mic-status {
width: 70%;
min-height: 15px;
max-height: 100px;
min-width: 45px;
max-width: 300px;
height: 100%;
border-radius: 5px;
background-color: #880808;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-family: play, sans-serif;
font-size: 2vw;
font-weight: bold;
text-transform: uppercase;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#mic-select {
width: 70%;
min-height: 20px;
max-height: 30px;
min-width: 45px;
max-width: 300px;
height: 100%;
font-family: play, sans-serif;
font-size: 1vw;
}
#mic-status.active {
background-color: #228B22;
}
#mic-status.active:before {
content: '';
display: none;
}
#mic-status.muted:before {
content: '';
display: none;
}
.stream {
font-family: play, sans-serif;
font-size: 2vw;
}
#title {
color: white;
font-family: play, sans-serif;
font-size: 2vw;
}
#streamer-conf {
font-family: play, sans-serif;
background-color: #222;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 5px;
font-size: 1vw;
text-transform: uppercase;
cursor: pointer;
}
#stream-status {
width: 70%;
min-height: 15px;
max-height: 100px;
min-width: 45px;
max-width: 300px;
height: 100%;
border-radius: 5px;
background-color: #880808;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-family: play, sans-serif;
font-size: 2vw;
font-weight: bold;
text-transform: uppercase;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#mic-select, #streamer {
display: block;
margin-bottom: 10px;
padding: 10px 0;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 1vw;
font-family: play, sans-serif;
width: auto;
height: 40px;
max-width: 100%;
line-height: 1.5;
}
#mic-select {
background-color: #f2f2f2;
color: #444;
max-height: 500px;
line-height: 0.75 !important;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
#streamer {
background-color: #f2f2f2;
color: #444;
}
#stream-status.green {
background-color: #228B22;
}
#stream-status.red {
background-color: #880808;
}
@font-face {
font-family: dotmatrix;
src: url(dotrice.regular.otf);
}
.left-column svg .markers {
fill: none;
stroke-width: 7;
stroke-linecap: round;
stroke-linejoin: bevel;
}
.left-column svg .five {
stroke: #228B22;
stroke-dasharray: 0, 91.6297857297;
}
.left-column svg .sixty {
stroke: #880808;
stroke-dasharray: 0, 16.7551608191;
}
.left-column svg .mask {
stroke: white;
stroke-dasharray: 1005.3096491488;
transform: rotateZ(-90deg);
transform-origin: center;
-webkit-animation: offset 60s linear infinite;
animation: offset 60s linear infinite;
}
@-webkit-keyframes offset {
from {
stroke-dashoffset: 1013.6872295584;
}
to {
stroke-dashoffset: 0;
}
}
@keyframes offset {
from {
stroke-dashoffset: 1013.6872295584;
}
to {
stroke-dashoffset: 0;
}
}
svg .hours.minutes, svg .seconds, svg .date {
fill: #FF0000;
font-size: 62pt;
font-family: play, sans-serif;
text-anchor: middle;
letter-spacing: -0.05em;
}
svg .seconds {
font-size:52pt;
}
svg .date {
font-size:28pt;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="left-column">
<!-- Clock -->
<svg version="1.1" viewBox="0 0 400 400" preserveAspectRatio="xMidYMax meet">
<defs>
<mask id="mask60s">
<circle class="markers mask" cx="200" cy="200" r="160"/>
</mask>
</defs>
<circle class="markers five" cx="200" cy="200" r="175"/>
<circle class="markers sixty" cx="200" cy="200" r="160" mask="url(#mask60s)"/>
<text class="date" x="200" y="135" dx="8">Dec 31</text>
<text class="hours minutes" x="200" y="225" dx="8"></text>
<text class="seconds" x="200" y="308" dx="8"></text>
</svg>
</div>
<div class="right-column">
<button id="settings-button">Show Settings</button>
<!-- Mic selector and status -->
<p id="title">Mic Status</p>
<select id="mic-select" class="hidden" style="display: none;"></select>
<p id="mic-status" class="muted">MUTED</p>
<!-- Stream selector and status -->
<p id="title">Stream Status</p>
<input type="text" id="streamer" class="hidden" value="CJ_Games_Live" style="display: none;">
<button id="streamer-conf" class="hidden" style="display: none;" onclick="updateStatus()">Confirm</button>
<p id="stream-status">OFFLINE</div>
</div>
</div>
<script>
// Get the microphone selection dropdown
const micSelect = document.getElementById("mic-select");
// Get the status element
const micStatus = document.getElementById("mic-status");
// Populate the microphone selection dropdown
navigator.mediaDevices.enumerateDevices().then(devices => {
const audioDevices = devices.filter(device => device.kind === "audioinput");
audioDevices.forEach(device => {
const option = document.createElement("option");
option.value = device.deviceId;
option.text = device.label || `Microphone ${micSelect.options.length + 1}`;
micSelect.appendChild(option);
});
});
// Check microphone status continuously
const checkMicStatus = () => {
const selectedMic = micSelect.value;
navigator.mediaDevices.getUserMedia({ audio: { deviceId: selectedMic } }).then(stream => {
// Check if microphone is muted
const audioCtx = new AudioContext();
const source = audioCtx.createMediaStreamSource(stream);
const analyser = audioCtx.createAnalyser();
source.connect(analyser);
analyser.fftSize = 32;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const updateStatus = () => {
analyser.getByteFrequencyData(dataArray);
const isMuted = dataArray.every(val => val === 0);
// Update status text and color
if (isMuted) {
micStatus.innerText = "MUTED";
micStatus.classList.add("muted");
micStatus.classList.remove("active");
} else {
micStatus.innerText = "ACTIVE";
micStatus.classList.add("active");
micStatus.classList.remove("muted");
}
};
setInterval(updateStatus, 100);
}).catch(error => {
console.error(error);
});
};
micSelect.addEventListener("change", checkMicStatus);
checkMicStatus();
function checkStatus(streamer) {
// Fetch the status of the webpage
fetch(`https://decapi.me/twitch/uptime/${streamer}`)
.then((response) => response.text())
.then((data) => {
// Update the UI based on the status
if (data.includes("offline")) {
document.getElementById("stream-status").style.backgroundColor = "#880808";
document.getElementById("stream-status").innerHTML = "Offline";
} else if (data.includes("Error")) {
document.getElementById("stream-status").style.backgroundColor = "#FAD02C";
document.getElementById("stream-status").innerHTML = "Error";
} else {
document.getElementById("stream-status").style.backgroundColor = "#228B22";
document.getElementById("stream-status").innerHTML = "Online";
}
})
.catch((error) => {
console.error("Error:", error);
});
}
checkStatus(document.getElementById("streamer").value);
// Call the checkStatus function every minute
setInterval(() => {
checkStatus(document.getElementById("streamer").value);
}, 60000);
function updateStatus() {
const streamer = document.getElementById("streamer").value;
checkStatus(streamer);
}
// month names
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const weekdayNames = ["Sun","Mon","Tues","Wed","Thur","Fri","Sat"];
// inital offsets
const oneSecond = 1000;
const now = new Date();
const offsetSeconds = now.getSeconds();
const offsetMilliSeconds = now.getMilliseconds();
const initialOffset = offsetSeconds + offsetMilliSeconds / oneSecond;
// set mask animation delay to account for the inital offset
document.querySelector( '.markers.mask' ).style.animationDelay = `-${initialOffset}s`
// update
const pad = num => `00${num}`.substr(-2);
const update = () => {
const now = new Date();
const hours = now.getHours();
const minutes = now.getMinutes();
const seconds = now.getSeconds();
const weekday = weekdayNames[now.getDay()];
const day = now.getDate();
const month = monthNames[now.getMonth()];
const date = weekday + ' ' + day + ' ' + month;
const $date = document.querySelector('svg .date');
const time = `${pad(hours)}:${pad(minutes)}`;
const $time = document.querySelector('svg .hours.minutes');
const secs = pad(seconds);
const $secs = document.querySelector('svg .seconds');
if (time !== $time.textContent)
$time.textContent = time;
if (secs !== $secs.textContent)
$secs.textContent = secs;
if (date !== $date.textContent)
$date.textContent = date;
};
// quick update
update();
// then start updating at the right time
setTimeout(
() => setInterval(update, oneSecond),
oneSecond - offsetMilliSeconds);
// Settings
const settingsButton = document.getElementById("settings-button");
const settingsInputs = [document.getElementById("mic-select"), document.getElementById("streamer"), document.getElementById("streamer-conf")];
settingsButton.addEventListener("click", () => {
for (let i = 0; i < settingsInputs.length; i++) {
if (settingsInputs[i].style.display === "none") {
settingsInputs[i].style.display = "block";
settingsButton.textContent = "Hide Settings";
} else {
settingsInputs[i].style.display = "none";
settingsButton.textContent = "Show Settings";
}
}
});
</script>
</body>
</html>