diff --git a/counters/mitour by antoshika/deps/countUp.umd.js b/counters/mitour by antoshika/deps/countUp.umd.js new file mode 100644 index 00000000..0aaa6b51 --- /dev/null +++ b/counters/mitour by antoshika/deps/countUp.umd.js @@ -0,0 +1,245 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = global || self, factory(global.countUp = {})); +}(this, (function (exports) { 'use strict'; + + var __assign = (undefined && undefined.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + var CountUp = (function () { + function CountUp(target, endVal, options) { + var _this = this; + this.target = target; + this.endVal = endVal; + this.options = options; + this.version = '2.0.7'; + this.defaults = { + startVal: 0, + decimalPlaces: 0, + duration: 2, + useEasing: true, + useGrouping: true, + smartEasingThreshold: 999, + smartEasingAmount: 333, + separator: ',', + decimal: '.', + prefix: '', + suffix: '' + }; + this.finalEndVal = null; + this.useEasing = true; + this.countDown = false; + this.error = ''; + this.startVal = 0; + this.paused = true; + this.count = function (timestamp) { + if (!_this.startTime) { + _this.startTime = timestamp; + } + var progress = timestamp - _this.startTime; + _this.remaining = _this.duration - progress; + if (_this.useEasing) { + if (_this.countDown) { + _this.frameVal = _this.startVal - _this.easingFn(progress, 0, _this.startVal - _this.endVal, _this.duration); + } + else { + _this.frameVal = _this.easingFn(progress, _this.startVal, _this.endVal - _this.startVal, _this.duration); + } + } + else { + if (_this.countDown) { + _this.frameVal = _this.startVal - ((_this.startVal - _this.endVal) * (progress / _this.duration)); + } + else { + _this.frameVal = _this.startVal + (_this.endVal - _this.startVal) * (progress / _this.duration); + } + } + if (_this.countDown) { + _this.frameVal = (_this.frameVal < _this.endVal) ? _this.endVal : _this.frameVal; + } + else { + _this.frameVal = (_this.frameVal > _this.endVal) ? _this.endVal : _this.frameVal; + } + _this.frameVal = Number(_this.frameVal.toFixed(_this.options.decimalPlaces)); + _this.printValue(_this.frameVal); + if (progress < _this.duration) { + _this.rAF = requestAnimationFrame(_this.count); + } + else if (_this.finalEndVal !== null) { + _this.update(_this.finalEndVal); + } + else { + if (_this.callback) { + _this.callback(); + } + } + }; + this.formatNumber = function (num) { + var neg = (num < 0) ? '-' : ''; + var result, x, x1, x2, x3; + result = Math.abs(num).toFixed(_this.options.decimalPlaces); + result += ''; + x = result.split('.'); + x1 = x[0]; + x2 = x.length > 1 ? _this.options.decimal + x[1] : ''; + if (_this.options.useGrouping) { + x3 = ''; + for (var i = 0, len = x1.length; i < len; ++i) { + if (i !== 0 && (i % 3) === 0) { + x3 = _this.options.separator + x3; + } + x3 = x1[len - i - 1] + x3; + } + x1 = x3; + } + if (_this.options.numerals && _this.options.numerals.length) { + x1 = x1.replace(/[0-9]/g, function (w) { return _this.options.numerals[+w]; }); + x2 = x2.replace(/[0-9]/g, function (w) { return _this.options.numerals[+w]; }); + } + return neg + _this.options.prefix + x1 + x2 + _this.options.suffix; + }; + this.easeOutExpo = function (t, b, c, d) { + return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b; + }; + this.options = __assign(__assign({}, this.defaults), options); + this.formattingFn = (this.options.formattingFn) ? + this.options.formattingFn : this.formatNumber; + this.easingFn = (this.options.easingFn) ? + this.options.easingFn : this.easeOutExpo; + this.startVal = this.validateValue(this.options.startVal); + this.frameVal = this.startVal; + this.endVal = this.validateValue(endVal); + this.options.decimalPlaces = Math.max( this.options.decimalPlaces); + this.resetDuration(); + this.options.separator = String(this.options.separator); + this.useEasing = this.options.useEasing; + if (this.options.separator === '') { + this.options.useGrouping = false; + } + this.el = (typeof target === 'string') ? document.getElementById(target) : target; + if (this.el) { + this.printValue(this.startVal); + } + else { + this.error = '[CountUp] target is null or undefined'; + } + } + CountUp.prototype.determineDirectionAndSmartEasing = function () { + var end = (this.finalEndVal) ? this.finalEndVal : this.endVal; + this.countDown = (this.startVal > end); + var animateAmount = end - this.startVal; + if (Math.abs(animateAmount) > this.options.smartEasingThreshold) { + this.finalEndVal = end; + var up = (this.countDown) ? 1 : -1; + this.endVal = end + (up * this.options.smartEasingAmount); + this.duration = this.duration / 2; + } + else { + this.endVal = end; + this.finalEndVal = null; + } + if (this.finalEndVal) { + this.useEasing = false; + } + else { + this.useEasing = this.options.useEasing; + } + }; + CountUp.prototype.start = function (callback) { + if (this.error) { + return; + } + this.callback = callback; + if (this.duration > 0) { + this.determineDirectionAndSmartEasing(); + this.paused = false; + this.rAF = requestAnimationFrame(this.count); + } + else { + this.printValue(this.endVal); + } + }; + CountUp.prototype.pauseResume = function () { + if (!this.paused) { + cancelAnimationFrame(this.rAF); + } + else { + this.startTime = null; + this.duration = this.remaining; + this.startVal = this.frameVal; + this.determineDirectionAndSmartEasing(); + this.rAF = requestAnimationFrame(this.count); + } + this.paused = !this.paused; + }; + CountUp.prototype.reset = function () { + cancelAnimationFrame(this.rAF); + this.paused = true; + this.resetDuration(); + this.startVal = this.validateValue(this.options.startVal); + this.frameVal = this.startVal; + this.printValue(this.startVal); + }; + CountUp.prototype.update = function (newEndVal) { + cancelAnimationFrame(this.rAF); + this.startTime = null; + this.endVal = this.validateValue(newEndVal); + if (this.endVal === this.frameVal) { + return; + } + this.startVal = this.frameVal; + if (!this.finalEndVal) { + this.resetDuration(); + } + this.finalEndVal = null; + this.determineDirectionAndSmartEasing(); + this.rAF = requestAnimationFrame(this.count); + }; + CountUp.prototype.printValue = function (val) { + var result = this.formattingFn(val); + if (this.el.tagName === 'INPUT') { + var input = this.el; + input.value = result; + } + else if (this.el.tagName === 'text' || this.el.tagName === 'tspan') { + this.el.textContent = result; + } + else { + this.el.innerHTML = result; + } + }; + CountUp.prototype.ensureNumber = function (n) { + return (typeof n === 'number' && !isNaN(n)); + }; + CountUp.prototype.validateValue = function (value) { + var newValue = Number(value); + if (!this.ensureNumber(newValue)) { + this.error = "[CountUp] invalid start or end value: " + value; + return null; + } + else { + return newValue; + } + }; + CountUp.prototype.resetDuration = function () { + this.startTime = null; + this.duration = Number(this.options.duration) * 1000; + this.remaining = this.duration; + }; + return CountUp; + }()); + + exports.CountUp = CountUp; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/counters/mitour by antoshika/deps/fonts.css b/counters/mitour by antoshika/deps/fonts.css new file mode 100644 index 00000000..eb7139c5 --- /dev/null +++ b/counters/mitour by antoshika/deps/fonts.css @@ -0,0 +1,79 @@ +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('./fonts/montserrat-400.woff2') format('woff2'); +} + +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('./fonts/montserrat-600.woff2') format('woff2'); +} + +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('./fonts/montserrat-700.woff2') format('woff2'); +} + +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url('./fonts/montserrat-800.woff2') format('woff2'); +} + +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url('./fonts/montserrat-900.woff2') format('woff2'); +} + +@font-face { + font-family: 'Manrope'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('./fonts/manrope-400.woff2') format('woff2'); +} + +@font-face { + font-family: 'Manrope'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('./fonts/manrope-500.woff2') format('woff2'); +} + +@font-face { + font-family: 'Manrope'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('./fonts/manrope-600.woff2') format('woff2'); +} + +@font-face { + font-family: 'Manrope'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('./fonts/manrope-700.woff2') format('woff2'); +} + +@font-face { + font-family: 'Manrope'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url('./fonts/manrope-800.woff2') format('woff2'); +} diff --git a/counters/mitour by antoshika/deps/fonts/manrope-400.woff2 b/counters/mitour by antoshika/deps/fonts/manrope-400.woff2 new file mode 100644 index 00000000..ba11e1bd Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/manrope-400.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/manrope-500.woff2 b/counters/mitour by antoshika/deps/fonts/manrope-500.woff2 new file mode 100644 index 00000000..90745bda Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/manrope-500.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/manrope-600.woff2 b/counters/mitour by antoshika/deps/fonts/manrope-600.woff2 new file mode 100644 index 00000000..ee7ac5e8 Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/manrope-600.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/manrope-700.woff2 b/counters/mitour by antoshika/deps/fonts/manrope-700.woff2 new file mode 100644 index 00000000..a21c3b0a Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/manrope-700.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/manrope-800.woff2 b/counters/mitour by antoshika/deps/fonts/manrope-800.woff2 new file mode 100644 index 00000000..2bfa6cb8 Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/manrope-800.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/montserrat-400.woff2 b/counters/mitour by antoshika/deps/fonts/montserrat-400.woff2 new file mode 100644 index 00000000..d13c06aa Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/montserrat-400.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/montserrat-600.woff2 b/counters/mitour by antoshika/deps/fonts/montserrat-600.woff2 new file mode 100644 index 00000000..9528cb3b Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/montserrat-600.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/montserrat-700.woff2 b/counters/mitour by antoshika/deps/fonts/montserrat-700.woff2 new file mode 100644 index 00000000..5c136d76 Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/montserrat-700.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/montserrat-800.woff2 b/counters/mitour by antoshika/deps/fonts/montserrat-800.woff2 new file mode 100644 index 00000000..b05b230e Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/montserrat-800.woff2 differ diff --git a/counters/mitour by antoshika/deps/fonts/montserrat-900.woff2 b/counters/mitour by antoshika/deps/fonts/montserrat-900.woff2 new file mode 100644 index 00000000..712dc4c8 Binary files /dev/null and b/counters/mitour by antoshika/deps/fonts/montserrat-900.woff2 differ diff --git a/counters/mitour by antoshika/favicon.svg b/counters/mitour by antoshika/favicon.svg new file mode 100644 index 00000000..cef4d3af --- /dev/null +++ b/counters/mitour by antoshika/favicon.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/counters/mitour by antoshika/index.html b/counters/mitour by antoshika/index.html new file mode 100644 index 00000000..8cd8da59 --- /dev/null +++ b/counters/mitour by antoshika/index.html @@ -0,0 +1,96 @@ + + + + + antoshika osu! Tournament Overlay + + + + + + + +
+
+
Mappool
+
+
+ +
+
PICK
+
+ +
+
+
+
+
+
+
Waiting for map...
+
+
+
Artist
+
+ +
+
+
CS0
+
AR0
+
OD0
+
SR0.0★
+
BPM0
+
Length0:00
+
ID0
+
+
+
+ +
+
+
+
+
+
+
+ +
+ WARMUP +
+
+ +
+
+
+
Red
+
+
+
+
+
0
+
+
+
+ +
VS
+ +
+
+
0
+
+
+
+
+
Blue
+
+
+
+
+ +
+
+
+ +
+ + + \ No newline at end of file diff --git a/counters/mitour by antoshika/js/socket.js b/counters/mitour by antoshika/js/socket.js new file mode 100644 index 00000000..d8710a08 --- /dev/null +++ b/counters/mitour by antoshika/js/socket.js @@ -0,0 +1,54 @@ +class Socket { + constructor(host) { + this.version = '0.1.5'; + + if (host) { + this.host = host; + } + + this.createConnection = this.createConnection.bind(this); + + this.sockets = {}; + } + + createConnection(url, callback, filters) { + let INTERVAL = ''; + + const that = this; + let counterPath = window.COUNTER_PATH || ""; + this.sockets[url] = new WebSocket(`ws://${this.host}${url}${url === '/websocket/commands' ? `?l=${encodeURI(counterPath)}` : ''}`); + + this.sockets[url].onopen = () => { + console.log(`[OPEN] ${url}: Connected`); + + if (INTERVAL) clearInterval(INTERVAL); + + if (url === '/websocket/commands') { + this.sockets[url].send(`getSettings:${encodeURI(counterPath)}`); + } + + if (Array.isArray(filters)) { + this.sockets[url].send(`applyFilters:${JSON.stringify(filters)}`); + } + }; + + this.sockets[url].onclose = (event) => { + console.log(`[CLOSED] ${url}: ${event.reason}`); + + delete this.sockets[url]; + INTERVAL = setTimeout(() => { + that.createConnection(url, callback, filters); + }, 1000); + }; + + this.sockets[url].onerror = (err) => { + console.error(`[ERROR] ${url}: ${err.message}`); + this.sockets[url].close(); + }; + + this.sockets[url].onmessage = (event) => { + const data = JSON.parse(event.data); + callback(data); + }; + }; +}; diff --git a/counters/mitour by antoshika/main.js b/counters/mitour by antoshika/main.js new file mode 100644 index 00000000..9455fd4a --- /dev/null +++ b/counters/mitour by antoshika/main.js @@ -0,0 +1,1237 @@ +let OSU_API_KEY = ""; + +let teamSeeds = {}; +let TEAMS_CONFIG = {}; + +function processBracketData(data) { + if (data && data.Teams) { + teamSeeds = {}; + data.Teams.forEach(t => { + if (t.FullName) teamSeeds[t.FullName.toLowerCase()] = t.Seed; + if (t.Acronym) teamSeeds[t.Acronym.toLowerCase()] = t.Seed; + }); + } + if (document.getElementById('slideContainer') && data) { + initSeedRevealWithData(data); + } +} + +const messageTimeCache = new Map(); + +let currentPlayer = ""; +let isAnimatingPlayer = false; + +window.onload = () => { + window.focus(); + if (document.body) document.body.focus(); + + const playerEl = document.getElementById("player"); + if(playerEl) fitTextToContainer(playerEl); + + if (!document.getElementById('player') && typeof TEAMS_CONFIG !== 'undefined' && DEBUG_MODE) { + const firstTeamName = Object.keys(TEAMS_CONFIG)[0]; + if (firstTeamName) showWinnerScene(firstTeamName, 'red'); + } + + const isMainOverlay = document.getElementById('mappool-overlay') && document.getElementById('top-bar'); + + let urlParams = new URLSearchParams(window.location.search); + let pathFromUrl = window.location.pathname.split('/')[1] || ""; + if (pathFromUrl) pathFromUrl = decodeURIComponent(pathFromUrl); + let currentPathForWelcome = window.COUNTER_PATH || urlParams.get('l') || pathFromUrl || "mitour by antoshika"; + + window.WELCOME_STORAGE_KEY = 'mitour_welcome_hidden_' + currentPathForWelcome; + + if (isMainOverlay && !localStorage.getItem(window.WELCOME_STORAGE_KEY)) { + const welcomeHtml = ` + +
+
+

🥬 Welcome to mitour!

+

Use the tosu settings panel to configure the bracket, mappool, and teams. Or you can read documentation.

+
+ Documentation +
+ + +
39!
+
+
+ `; + document.body.insertAdjacentHTML('beforeend', welcomeHtml); + } +}; + +window.closeWelcome = function() { + const dontShow = document.getElementById('welcomeDontShow').checked; + if (dontShow) localStorage.setItem(window.WELCOME_STORAGE_KEY, 'true'); + const el = document.getElementById('welcomeOverlay'); + el.style.opacity = '0'; + setTimeout(() => el.remove(), 400); +}; + +window.addEventListener('resize', () => { + const playerEl = document.getElementById("player"); + if(playerEl) { + playerEl.style.transform = "scale(1)"; + fitTextToContainer(playerEl); + } +}); + +window.addEventListener('mousedown', () => { + window.focus(); + document.body.focus(); +}); + +window.addEventListener('keydown', (e) => { + if (!document.getElementById('slideContainer')) { + if (e.key === 'm' || e.key === 'M' || + e.key === 'ь' || e.key === 'Ь' || + e.code === 'KeyM' || e.keyCode === 77) { + toggleMappool(); + } + } +}); + +let urlParams = new URLSearchParams(window.location.search); +let pathFromUrl = window.location.pathname.split('/')[1] || ""; +if (pathFromUrl) pathFromUrl = decodeURIComponent(pathFromUrl); +window.COUNTER_PATH = window.COUNTER_PATH || urlParams.get('l') || pathFromUrl || "mitour by antoshika"; + +const socket = new Socket(window.location.host); + +socket.createConnection("/ws", (data) => { + if (document.getElementById('slideContainer')) return; + + try { + let pName = ""; + try { + if (data.resultsScreen && data.resultsScreen.name) { + pName = data.resultsScreen.name; + } else if (data.gameplay && data.gameplay.name) { + pName = data.gameplay.name; + } + + if (pName && pName.trim() !== "") { + if (pName !== currentPlayer && !isAnimatingPlayer) { + updatePlayerName(pName); + } + } + } catch (e) {} + + updateOverlay(data); + + if (!DEBUG_MODE && data.tourney && data.tourney.manager) { + handleWinnerData(data.tourney.manager); + } + } catch (err) { } +}); + +socket.createConnection("/websocket/commands", (data) => { + try { + if (data.command === 'getSettings') { + const msg = data.message; + if (msg.osuApiKey && msg.osuApiKey.trim() !== "") { + OSU_API_KEY = msg.osuApiKey.trim(); + } + if (msg.bracketData && msg.bracketData.trim() !== "" && msg.bracketData !== "{}") { + try { + const parsed = JSON.parse(msg.bracketData); + processBracketData(parsed); + } catch(e) {} + } + if (msg.mappoolData && msg.mappoolData.trim() !== "" && msg.mappoolData !== "{}") { + try { + const lines = msg.mappoolData.split('\n'); + const newPicks = {}; + lines.forEach(line => { + const parts = line.split(':'); + if (parts.length === 2) { + const mod = parts[0].trim(); + const id = parts[1].trim(); + if (mod && id) newPicks[id] = mod; + } + }); + picksData = newPicks; + if(document.getElementById('pool-grid')) { + renderMappool(); + fetchMapDetails(); + } + if(document.getElementById('pick-bar')) { + const pickName = picksData[String(currentMapId)] || "WARMUP"; + updatePickBar(pickName); + } + if(document.getElementById('inner-pick-badge')) { + checkInnerPickBadge(currentMapId); + } + } catch(e) {} + } + if (msg.teamsData && msg.teamsData.trim() !== "" && msg.teamsData !== "{}") { + try { + const lines = msg.teamsData.split('\n'); + const newTeams = {}; + lines.forEach(line => { + const parts = line.split(':'); + if (parts.length >= 2) { + const teamName = parts.shift().trim(); + const idsStr = parts.join(':'); + const ids = idsStr.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + if (teamName && ids.length > 0) newTeams[teamName] = ids; + } + }); + TEAMS_CONFIG = newTeams; + } catch(e) {} + } + } + } catch(e) {} +}); + +let currentSetId = -1; +let currentMapId = -1; +let picksData = {}; +const mapCache = {}; +const mapStates = {}; +let lastChatCount = 0; + +const countUpOptions = { duration: 0.8, useEasing: true, separator: ' ' }; +let redCountUp = null; +let blueCountUp = null; + +let currentWinner = null; +let isDataLoading = false; +const DEBUG_MODE = false; + +if (typeof countUp !== 'undefined') { + if(document.getElementById('red-score')) { + redCountUp = new countUp.CountUp('red-score', 0, countUpOptions); + blueCountUp = new countUp.CountUp('blue-score', 0, countUpOptions); + redCountUp.start(); + blueCountUp.start(); + } +} + + +function updatePlayerName(newText) { + const el = document.getElementById("player"); + if(!el) return; + + isAnimatingPlayer = true; + el.classList.add("hidden"); + + setTimeout(() => { + el.style.transform = "scale(1)"; + el.innerText = newText; + + requestAnimationFrame(() => { + fitTextToContainer(el); + + el.classList.remove("hidden"); + currentPlayer = newText; + + setTimeout(() => { isAnimatingPlayer = false; }, 300); + }); + }, 300); +} + +function fitTextToContainer(el) { + if (!el.parentElement) return; + + el.style.transform = "scale(1)"; + const containerWidth = el.parentElement.offsetWidth; + const actualWidth = el.scrollWidth; + + if (actualWidth === 0 || containerWidth === 0) return; + + if (actualWidth > containerWidth) { + const scale = containerWidth / actualWidth; + el.style.transform = `scale(${scale})`; + } +} + +const LOCAL_IMG_CACHE_KEY = 'mitour_team_img_exts'; +let persistentExtCache = {}; +try { + persistentExtCache = JSON.parse(localStorage.getItem(LOCAL_IMG_CACHE_KEY) || '{}'); +} catch(e) {} + +const teamImageCache = {}; + +function setImageWithFallback(element, teamNameRaw, isImgTag = false) { + if (!teamNameRaw) return; + const teamName = teamNameRaw.trim(); + + if (teamImageCache[teamName]) { + const src = teamImageCache[teamName]; + if (src === 'none' || src === 'loading') { + if (src === 'none') { + if (isImgTag) element.style.display = 'none'; + else element.style.backgroundImage = 'none'; + } + return; + } + if (isImgTag) { + element.src = src; + element.style.display = 'block'; + } else { + element.style.backgroundImage = `url('${src}')`; + } + return; + } + + teamImageCache[teamName] = 'loading'; + + let exts = ['png', 'jpg', 'jpeg', 'gif', 'webp']; + + const knownExt = persistentExtCache[teamName]; + if (knownExt && exts.includes(knownExt)) { + exts = [knownExt, ...exts.filter(e => e !== knownExt)]; + } + + let cur = 0; + + function tryNext() { + if (cur >= exts.length) { + teamImageCache[teamName] = 'none'; + if (isImgTag) element.style.display = 'none'; + else element.style.backgroundImage = 'none'; + return; + } + + const ext = exts[cur]; + const src = `imgs/${encodeURIComponent(teamName)}.${ext}`; + + const img = new Image(); + img.onload = () => { + teamImageCache[teamName] = src; + persistentExtCache[teamName] = ext; + try { + localStorage.setItem(LOCAL_IMG_CACHE_KEY, JSON.stringify(persistentExtCache)); + } catch(e) {} + + if (isImgTag) { + element.src = src; + element.style.display = 'block'; + } else { + element.style.backgroundImage = `url('${src}')`; + } + }; + img.onerror = () => { + cur++; + tryNext(); + }; + img.src = src; + } + + tryNext(); +} + +function updateOverlay(data) { + const chatOverlay = document.getElementById('chat-overlay'); + if (chatOverlay) { + let isPlaying = false; + if (data.menu && data.menu.state === 2) isPlaying = true; + if (data.tourney && data.tourney.manager && data.tourney.manager.ipcState === 3) isPlaying = true; + + if (isPlaying) chatOverlay.classList.add('hidden'); + else chatOverlay.classList.remove('hidden'); + } + + if (data.menu && data.menu.bm) { + const bm = data.menu.bm; + const metadata = bm.metadata; + const stats = bm.stats; + const time = bm.time; + + const titleEl = document.getElementById('title'); + const titleWrap = document.querySelector('.title-overflow-wrap'); + + if(titleEl && titleWrap) { + const newTitle = metadata.title || ""; + if(titleEl.innerText !== newTitle) { + titleEl.classList.remove('scrolling'); + titleEl.style.removeProperty('--text-width'); + titleEl.style.removeProperty('--parent-width'); + titleEl.innerText = newTitle; + void titleEl.offsetWidth; + + if (titleEl.scrollWidth > titleWrap.clientWidth) { + titleEl.style.setProperty('--text-width', `${titleEl.scrollWidth}px`); + titleEl.style.setProperty('--parent-width', `${titleWrap.clientWidth}px`); + titleEl.classList.add('scrolling'); + } + } + } + + if(document.getElementById('artist')) document.getElementById('artist').innerText = metadata.artist || ""; + if(document.getElementById('mapid')) document.getElementById('mapid').innerText = bm.id || 0; + + if(document.getElementById('cs')) document.getElementById('cs').innerText = stats.CS != null ? stats.CS : 0; + if(document.getElementById('ar')) document.getElementById('ar').innerText = stats.AR != null ? stats.AR : 0; + if(document.getElementById('od')) document.getElementById('od').innerText = stats.OD != null ? stats.OD : 0; + if(document.getElementById('sr')) document.getElementById('sr').innerText = (stats.fullSR != null ? stats.fullSR : 0).toFixed(2) + "★"; + if(document.getElementById('bpm')) document.getElementById('bpm').innerText = Math.round(stats.BPM != null ? stats.BPM.max : 0); + + const currentMs = time.current; + const totalMs = time.full; + if(document.getElementById('len')) document.getElementById('len').innerText = `${formatTime(currentMs)} / ${formatTime(totalMs)}`; + + let progressPercent = 0; + if (totalMs > 0) progressPercent = (currentMs / totalMs) * 100; + if (progressPercent > 100) progressPercent = 100; + if (progressPercent < 0) progressPercent = 0; + + if(document.getElementById('progress-fill')) document.getElementById('progress-fill').style.width = `${progressPercent}%`; + + if (bm.set !== currentSetId) { + currentSetId = bm.set; + let bgUrl = currentSetId > 0 + ? `https://assets.ppy.sh/beatmaps/${currentSetId}/covers/cover@2x.jpg` + : `http://127.0.0.1:24050/Background?time=${new Date().getTime()}`; + + const thumbDiv = document.getElementById('map-thumb'); + const bgDiv = document.getElementById('card-bg'); + + if(thumbDiv) thumbDiv.style.backgroundImage = `url('${bgUrl}')`; + if(bgDiv) bgDiv.style.backgroundImage = `url('${bgUrl}')`; + } + + if (bm.id !== currentMapId) { + currentMapId = bm.id; + const pickName = picksData[String(bm.id)] || "WARMUP"; + if(document.getElementById('pick-bar')) updatePickBar(pickName); + if(document.getElementById('inner-pick-badge')) checkInnerPickBadge(bm.id); + } + + if(document.getElementById('map-status')) { + updateMapStatus(bm.rankedStatus); + } + } + + if (data.tourney && data.tourney.manager) { + const manager = data.tourney.manager; + const ipc = data.tourney.ipcClients; + const leftNameRaw = manager.teamName.left || "Red"; + const rightNameRaw = manager.teamName.right || "Blue"; + + let leftSeed = teamSeeds[leftNameRaw.toLowerCase()]; + let rightSeed = teamSeeds[rightNameRaw.toLowerCase()]; + + let leftDisplay = leftSeed ? `#${leftSeed} ${leftNameRaw}` : leftNameRaw; + let rightDisplay = rightSeed ? `${rightNameRaw} #${rightSeed}` : rightNameRaw; + + if(document.getElementById('red-name')) document.getElementById('red-name').innerHTML = leftDisplay; + if(document.getElementById('blue-name')) document.getElementById('blue-name').innerHTML = rightDisplay; + if(document.getElementById('red-icon')) setImageWithFallback(document.getElementById('red-icon'), leftNameRaw); + if(document.getElementById('blue-icon')) setImageWithFallback(document.getElementById('blue-icon'), rightNameRaw); + if(document.getElementById('red-stars')) renderStars('red-stars', manager.stars.left, manager.bestOF); + if(document.getElementById('blue-stars')) renderStars('blue-stars', manager.stars.right, manager.bestOF); + + let redS = 0, blueS = 0; + if (ipc) { + ipc.forEach(c => { + let currentScore = (c.gameplay && c.gameplay.score) ? c.gameplay.score : 0; + if (c.team === 'left' || c.team === 'Red') redS += currentScore; + if (c.team === 'right' || c.team === 'Blue') blueS += currentScore; + }); + } + if (redCountUp) redCountUp.update(redS); + else if(document.getElementById('red-score')) document.getElementById('red-score').innerText = redS; + + if (blueCountUp) blueCountUp.update(blueS); + else if(document.getElementById('blue-score')) document.getElementById('blue-score').innerText = blueS; + + const diff = Math.abs(redS - blueS); + const redDiffEl = document.getElementById('red-diff'); + const blueDiffEl = document.getElementById('blue-diff'); + + if(redDiffEl && blueDiffEl) { + redDiffEl.innerText = ""; + blueDiffEl.innerText = ""; + redDiffEl.classList.remove('visible'); + blueDiffEl.classList.remove('visible'); + + if (redS > blueS) { + blueDiffEl.innerText = `-${diff.toLocaleString()}`; + blueDiffEl.classList.add('visible'); + } else if (blueS > redS) { + redDiffEl.innerText = `-${diff.toLocaleString()}`; + redDiffEl.classList.add('visible'); + } + } + + if(document.getElementById('team-red')) document.getElementById('team-red').classList.toggle('leading', redS > blueS); + if(document.getElementById('team-blue')) document.getElementById('team-blue').classList.toggle('leading', blueS > redS); + } + + let chatSource = null; + if (data.tourney) { + if (data.tourney.chat) chatSource = data.tourney.chat; + else if (data.tourney.manager && data.tourney.manager.chat) chatSource = data.tourney.manager.chat; + } else if (data.chat) { + chatSource = data.chat; + } + + if (chatSource) updateChat(chatSource); +} + +function updateMapStatus(status) { + const el = document.getElementById('map-status'); + if (!el) return; + el.className = 'status-badge'; + let activeClass = ''; + if (status === 4) activeClass = 'status-ranked'; + else if (status === 7) activeClass = 'status-loved'; + else if (status === 3 || status === 6) activeClass = 'status-qualified'; + else if (status === 5) activeClass = 'status-approved'; + else if (status === 2 || status === 0) activeClass = 'status-pending'; + else if (status === -1) activeClass = 'status-wip'; + else if (status === -2) activeClass = 'status-graveyard'; + else if (status === 1 || status === null || status === undefined) activeClass = 'status-none'; + if (activeClass) el.classList.add(activeClass, 'visible'); + else el.classList.remove('visible'); +} + +function handleWinnerData(manager) { + if (!document.getElementById('winner-container')) return; + const bestOf = manager.bestOF; + const winsNeeded = Math.ceil(bestOf / 2); + const starsLeft = manager.stars.left; + const starsRight = manager.stars.right; + + let winnerTeam = null; + let winnerSide = null; + + if (starsLeft >= winsNeeded) { + winnerTeam = manager.teamName.left; + winnerSide = 'red'; + } else if (starsRight >= winsNeeded) { + winnerTeam = manager.teamName.right; + winnerSide = 'blue'; + } else { + hideWinner(); + return; + } + + if (winnerTeam && winnerTeam !== currentWinner) { + currentWinner = winnerTeam; + showWinnerScene(winnerTeam, winnerSide); + } +} + +async function showWinnerScene(teamName, side) { + const container = document.getElementById('winner-container'); + if(!container) return; + isDataLoading = true; + const labelEl = document.getElementById('team-label'); + const nameEl = document.getElementById('team-name'); + const logoEl = document.getElementById('team-logo'); + + container.classList.remove('winner-red', 'winner-blue'); + container.classList.add(side === 'red' ? 'winner-red' : 'winner-blue'); + labelEl.innerText = side === 'red' ? "TEAM RED" : "TEAM BLUE"; + + let seed = teamSeeds[teamName.toLowerCase()]; + if (seed) { + nameEl.innerHTML = `#${seed} ${teamName}`; + } else { + nameEl.innerText = teamName; + } + + setImageWithFallback(logoEl, teamName); + + let playerIds = TEAMS_CONFIG[teamName]; + if (!playerIds) { + const key = Object.keys(TEAMS_CONFIG).find(k => k.toLowerCase() === teamName.toLowerCase()); + if (key) playerIds = TEAMS_CONFIG[key]; + } + if (playerIds) { + try { + const players = await Promise.all(playerIds.map(id => fetchPlayer(id))); + renderPlayers(players); + } catch (e) { console.error(e); } + } else { + document.getElementById('players-grid').innerHTML = '
No Data
'; + } + container.classList.remove('hidden'); + isDataLoading = false; +} + +function hideWinner() { + const container = document.getElementById('winner-container'); + if (container && !container.classList.contains('hidden')) { + container.classList.add('hidden'); + currentWinner = null; + } +} + +async function fetchPlayer(userId) { + if (userId == 18815482) return { name: "antoshika", country: "RU", rank: 0 }; + if (!userId || isNaN(userId)) return { name: "Unknown", country: null, rank: 0 }; + if (OSU_API_KEY) { + try { + const res = await fetch(`https://osu.ppy.sh/api/get_user?k=${OSU_API_KEY}&u=${userId}&type=id`); + if (res.ok) { + const json = await res.json(); + if (json && json.length > 0) return { name: json[0].username, country: json[0].country, rank: parseInt(json[0].pp_rank) || 0 }; + } + } catch (e) { } + } + try { + const res = await fetch(`https://api.nerinyan.moe/u/${userId}`); + if (res.ok) { + const json = await res.json(); + return { name: json.username, country: json.country_code, rank: json.statistics ? json.statistics.global_rank : 0 }; + } + } catch (e) { } + try { + const res = await fetch(`https://catboy.best/api/v2/user/${userId}`); + if (res.ok) { + const json = await res.json(); + return { name: json.username, country: json.country, rank: json.statistics ? json.statistics.global_rank : 0 }; + } + } catch (e) { } + return { name: `Player ${userId}`, country: null, rank: 0 }; +} + +function renderPlayers(players) { + const grid = document.getElementById('players-grid'); + if(!grid) return; + grid.innerHTML = ''; + players.forEach(p => { + const div = document.createElement('div'); + div.className = 'player-item'; + let flagHtml = ''; + if (p.country) { + const countryCode = p.country.toLowerCase(); + flagHtml = `${p.country}`; + } + div.innerHTML = `${flagHtml} ${p.name}`; + grid.appendChild(div); + }); +} + +function checkInnerPickBadge(mapId) { + const badge = document.getElementById('inner-pick-badge'); + if(!badge) return; + const status = mapStates[mapId]; + badge.classList.remove('red', 'blue', 'visible'); + if (status === 'picked-red') { + badge.innerText = "PICK"; + badge.classList.add('red', 'visible'); + } else if (status === 'picked-blue') { + badge.innerText = "PICK"; + badge.classList.add('blue', 'visible'); + } +} + +function updateBadgeIfCurrent(clickedMapId) { + if (Number(clickedMapId) === Number(currentMapId)) checkInnerPickBadge(clickedMapId); +} + +function updatePickBar(pickText) { + const bar = document.getElementById('pick-bar'); + const textEl = document.getElementById('pick-text'); + const topBar = document.getElementById('top-bar'); + + if(textEl) textEl.innerText = pickText; + let colorVar = getColorForMod(pickText); + const colorVal = `var(${colorVar})`; + + if(topBar) { + topBar.style.setProperty('--active-color', colorVal); + + if (pickText === "WARMUP") { + topBar.style.borderColor = "rgba(255,255,255,0.1)"; + } else { + topBar.style.borderColor = colorVal; + } + } +} + +function getColorForMod(modStr) { + if (modStr === "WARMUP") return '--mod-warmup'; + if (modStr.startsWith("NM")) return '--mod-nm'; + if (modStr.startsWith("HD")) return '--mod-hd'; + if (modStr.startsWith("HR")) return '--mod-hr'; + if (modStr.startsWith("DT")) return '--mod-dt'; + if (modStr.startsWith("FM")) return '--mod-fm'; + if (modStr.startsWith("AP")) return '--mod-ap'; + if (modStr.startsWith("TB")) return '--mod-tb'; + if (modStr.startsWith("EZ")) return '--mod-ez'; + return '--mod-def'; +} + +function formatTime(ms) { + if (isNaN(ms) || ms < 0) return "0:00"; + const totalSeconds = Math.floor(ms / 1000); + const m = Math.floor(totalSeconds / 60); + const s = totalSeconds % 60; + return `${m}:${s < 10 ? '0' : ''}${s}`; +} + +function renderStars(elementId, currentWins, bestOf) { + const container = document.getElementById(elementId); + if(!container) return; + container.innerHTML = ''; + const winsNeeded = Math.ceil(bestOf / 2); + for (let i = 0; i < winsNeeded; i++) { + let div = document.createElement('div'); + div.className = 'star-point'; + if (i < currentWins) div.classList.add('active'); + container.appendChild(div); + } +} + +function toggleMappool() { + const el = document.getElementById('mappool-overlay'); + if(el) el.classList.toggle('visible'); +} + +async function fetchMapDetails() { + const ids = Object.keys(picksData); + const BATCH_SIZE = 5; + for (let i = 0; i < ids.length; i += BATCH_SIZE) { + const batch = ids.slice(i, i + BATCH_SIZE); + await Promise.all(batch.map(id => loadSingleMap(id))); + } +} + +async function loadSingleMap(id) { + if (mapCache[id]) return; + try { + const response = await fetch(`https://api.nerinyan.moe/b/${id}`); + if (!response.ok) throw new Error("Err"); + const data = await response.json(); + let mapper = data.beatmapset ? data.beatmapset.creator : "Unknown"; + let diffName = data.version; + if (!diffName || diffName === "Undefined") diffName = "Unknown"; + mapCache[id] = { title: data.title, artist: data.artist, mapper: mapper, diff: diffName, setId: data.beatmapset_id }; + } catch (error) { + try { + const sayoRes = await fetch(`https://api.sayobot.cn/v2/beatmapinfo?K=${id}`); + const sayoJson = await sayoRes.json(); + if(sayoJson && sayoJson.data) { + let diffName = "Unknown"; + if (sayoJson.data.bid_data) { + const bData = sayoJson.data.bid_data.find(b => String(b.bid) === String(id)); + if (bData && bData.version && bData.version !== "Undefined") diffName = bData.version; + else if (sayoJson.data.bid_data.length > 0) diffName = sayoJson.data.bid_data[0].version; + } + mapCache[id] = { title: sayoJson.data.title, artist: sayoJson.data.artist, mapper: sayoJson.data.creator, diff: diffName, setId: sayoJson.data.sid }; + } + } catch (e2) { + mapCache[id] = { title: `Map ID: ${id}`, artist: "Artist", mapper: "Mapper", diff: "Unknown", setId: 0 }; + } + } + updateCardUI(id); +} + +function updateCardUI(id) { + const card = document.querySelector(`.pool-card[data-id="${id}"]`); + if (card && mapCache[id]) { + const info = mapCache[id]; + const bg = card.querySelector('.pool-card-bg'); + if (info.setId > 0) bg.style.backgroundImage = `url('https://assets.ppy.sh/beatmaps/${info.setId}/covers/cover.jpg')`; + else bg.style.backgroundColor = '#333'; + card.querySelector('.pc-title').innerText = info.title; + card.querySelector('.pc-artist').innerText = info.artist; + card.querySelector('.pc-mapper').innerHTML = `mapper ${info.mapper} diff ${info.diff}`; + } +} + +function renderMappool() { + const container = document.getElementById('pool-grid'); + if(!container) return; + container.innerHTML = ''; + const groups = { NM: [], HD: [], HR: [], DT: [], EZ: [], FM: [], AP: [], TB: [] }; + Object.entries(picksData).forEach(([id, modStr]) => { + let prefix = modStr.replace(/[0-9]/g, ''); + if (groups[prefix]) groups[prefix].push({ id, modStr }); + }); + const order = ['NM', 'HD', 'HR', 'DT', 'EZ', 'FM', 'AP', 'TB']; + order.forEach(modTag => { + const maps = groups[modTag]; + if (maps.length === 0) return; + maps.sort((a, b) => a.modStr.localeCompare(b.modStr, undefined, {numeric: true})); + const row = document.createElement('div'); + row.className = 'pool-row'; + maps.forEach(item => { + const card = document.createElement('div'); + card.className = 'pool-card'; + card.dataset.id = item.id; + let colorVar = getColorForMod(item.modStr); + card.innerHTML = ` +
+
+
+
+
+
Loading...
+
Map ID: ${item.id}
+
wait...
+
+
+ ${item.modStr} +
+
+ `; + card.onmousedown = (e) => handlePoolClick(card, e, item.id); + row.appendChild(card); + }); + container.appendChild(row); + }); +} + +function handlePoolClick(card, e, mapId) { + e.preventDefault(); + const classes = ['picked-red', 'picked-blue', 'banned-red', 'banned-blue', 'protect-red', 'protect-blue']; + let newStatus = null; + if (e.shiftKey) { + classes.forEach(c => card.classList.remove(c)); + mapStates[mapId] = null; + updateBadgeIfCurrent(mapId); + return; + } + classes.forEach(c => card.classList.remove(c)); + if (e.altKey) { + if (e.button === 0) { card.classList.add('protect-red'); newStatus = 'protect-red'; } + if (e.button === 2) { card.classList.add('protect-blue'); newStatus = 'protect-blue'; } + } else if (e.ctrlKey) { + if (e.button === 0) { card.classList.add('banned-red'); newStatus = 'banned-red'; } + if (e.button === 2) { card.classList.add('banned-blue'); newStatus = 'banned-blue'; } + } else { + if (e.button === 0) { card.classList.add('picked-red'); newStatus = 'picked-red'; } + if (e.button === 2) { card.classList.add('picked-blue'); newStatus = 'picked-blue'; } + } + mapStates[mapId] = newStatus; + updateBadgeIfCurrent(mapId); +} + +function updateChat(chatData) { + if (!chatData) return; + if (chatData.length === lastChatCount) return; + const container = document.getElementById('chat-messages'); + if(!container) return; + container.innerHTML = ''; + const startIndex = Math.max(chatData.length - 8, 0); + const messagesToShow = chatData.slice(startIndex); + messagesToShow.forEach((msg, idx) => { + const originalIndex = startIndex + idx; + let timeStr; + if (!messageTimeCache.has(originalIndex)) { + const now = new Date(); + timeStr = now.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); + messageTimeCache.set(originalIndex, timeStr); + } + timeStr = messageTimeCache.get(originalIndex); + let teamClass = 'team-unknown'; + let roleClass = ''; + if (msg.team === 'left' || msg.team === 'Red') teamClass = 'team-left'; + else if (msg.team === 'right' || msg.team === 'Blue') teamClass = 'team-right'; + if (msg.name === 'BanchoBot') roleClass = 'role-banchobot'; + else if (teamClass === 'team-unknown') roleClass = 'role-referee'; + let messageText = msg.messageBody || msg.message || ""; + if (!messageText) return; + const line = document.createElement('div'); + line.className = 'chat-irc-line'; + line.innerHTML = `[${timeStr}]${msg.name}: ${messageText}`; + container.appendChild(line); + }); + lastChatCount = chatData.length; +} + +document.addEventListener('contextmenu', event => event.preventDefault()); + +const SEED_CONFIG = { jsonPath: 'conf/bracket.json' }; +const seedMapCache = new Map(); +let seedTeams = []; +let seedCurrentTeamIndex = 0; +const MOD_ORDER = ['NM', 'HD', 'HR', 'DT', 'EZ', 'FM', 'AP', 'TB']; + +window.nextSlide = async function() { + if (seedCurrentTeamIndex > 0) { + seedCurrentTeamIndex--; + await updateSlideWithAnimation(); + } +}; + +window.prevSlide = async function() { + if (seedCurrentTeamIndex < seedTeams.length - 1) { + seedCurrentTeamIndex++; + await updateSlideWithAnimation(); + } +}; + +async function initSeedRevealWithData(data) { + try { + if (!data || !data.Teams) throw new Error("Teams not found"); + seedTeams = data.Teams.map(t => { + let groupedMaps = {}; + MOD_ORDER.forEach(mod => groupedMaps[mod] = []); + groupedMaps['OTHER'] = []; + if (t.SeedingResults) { + t.SeedingResults.forEach(group => { + let modName = group.Mod; + let targetList = groupedMaps[modName] ? groupedMaps[modName] : groupedMaps['OTHER']; + if(group.Beatmaps) { + group.Beatmaps.forEach((m, idx) => { + const poolId = `${modName}${idx + 1}`; + let embeddedInfo = null; + if (m.BeatmapInfo && m.BeatmapInfo.Metadata) { + embeddedInfo = { + title: m.BeatmapInfo.Metadata.Title, + artist: m.BeatmapInfo.Metadata.Artist, + mapper: m.BeatmapInfo.Metadata.Author.Username, + diff: m.BeatmapInfo.DifficultyName, + cover: m.BeatmapInfo.Covers ? m.BeatmapInfo.Covers['cover@2x'] : null + }; + } + targetList.push({ mod: modName, poolId: poolId, seed: parseInt(m.Seed, 10) || m.Seed, score: m.Score, id: m.ID, info: embeddedInfo }); + }); + } + }); + } + let teamFlagUpper = 'XX'; + if (t.FlagName) teamFlagUpper = t.FlagName.toUpperCase(); + let roster = []; + if (t.Players) { + roster = t.Players.map(p => { + let cCode = 'XX'; + if (p.country_code) cCode = p.country_code; + else if (p.country && p.country.code) cCode = p.country.code; + if (cCode) cCode = cCode.toUpperCase(); + let pName = p.Username || p.username || p.Name || p.name; + if (!pName && t.Players.length === 1) pName = t.FullName; + let pId = p.id || p.ID; + return { name: pName || (pId ? `Player ${pId}` : null), country: cCode, id: pId }; + }).filter(p => p.name !== null); + } + return { + ...t, + _seed: parseInt(t.Seed, 10), + _avgRank: t.AverageRank ? Math.round(t.AverageRank) : 0, + _groupedMaps: groupedMaps, + _roster: roster, + _acronym: t.Acronym, + _fullname: t.FullName, + _flagName: teamFlagUpper + }; + }); + seedTeams.sort((a, b) => a._seed - b._seed); + seedCurrentTeamIndex = seedTeams.length - 1; + await preloadMapsForTeam(seedTeams[seedCurrentTeamIndex]); + renderSlide(); + + const container = document.getElementById('slideContainer'); + if (container) container.classList.remove('seed-hidden'); + + document.addEventListener('keydown', (e) => { + if (e.code === 'Space' || e.code === 'ArrowRight') nextSlide(); + if (e.code === 'ArrowLeft') prevSlide(); + }); + } catch (e) { + console.error(e); + document.body.innerHTML = "

Error: " + e.message + "

"; + } +} + +async function fetchBeatmapData(id) { + if (OSU_API_KEY) { + try { + const res = await fetch(`https://osu.ppy.sh/api/get_beatmaps?k=${OSU_API_KEY}&b=${id}`); + if (res.ok) { + const json = await res.json(); + if (json && json.length > 0) { + const data = json[0]; + return { + title: data.title, + artist: data.artist, + mapper: data.creator, + diff: data.version, + setId: data.beatmapset_id, + cover: `https://assets.ppy.sh/beatmaps/${data.beatmapset_id}/covers/cover.jpg` + }; + } + } + } catch(e) {} + } + + try { + const sayoRes = await fetch(`https://api.sayobot.cn/v2/beatmapinfo?K=${id}&T=4`); + if (sayoRes.ok) { + const sayoJson = await sayoRes.json(); + if(sayoJson && sayoJson.data) { + let diffName = "Unknown"; + if (sayoJson.data.bid_data) { + const bData = sayoJson.data.bid_data.find(b => String(b.bid) === String(id)); + if (bData && bData.version && bData.version !== "Undefined") diffName = bData.version; + else if (sayoJson.data.bid_data.length > 0) diffName = sayoJson.data.bid_data[0].version; + } + return { + title: sayoJson.data.title, + artist: sayoJson.data.artist, + mapper: sayoJson.data.creator, + diff: diffName, + setId: sayoJson.data.sid, + cover: `https://assets.ppy.sh/beatmaps/${sayoJson.data.sid}/covers/cover.jpg` + }; + } + } + } catch (e2) {} + + return { title: `Map ID: ${id}`, artist: "Artist", mapper: "Mapper", diff: "Unknown", setId: 0, cover: null }; +} + +async function loadSingleMap(id) { + if (mapCache[id]) return; + const data = await fetchBeatmapData(id); + mapCache[id] = data; + updateCardUI(id); +} + +async function getMapInfo(beatmapId) { + if (seedMapCache.has(beatmapId)) return seedMapCache.get(beatmapId); + const data = await fetchBeatmapData(beatmapId); + seedMapCache.set(beatmapId, data); + return data; +} + +async function preloadMapsForTeam(team) { + if (!team) return; + const promises = []; + if (team._groupedMaps) { + for (const mod in team._groupedMaps) { + team._groupedMaps[mod].forEach(m => { + if (!m.info && m.id) { + promises.push(getMapInfo(m.id).then(data => { + m.info = data; + })); + } + }); + } + } + if (team._roster) { + team._roster.forEach(p => { + if ((p.name && p.name.startsWith('Player ')) || !p._rankFetched) { + promises.push(fetchPlayer(p.id).then(data => { + p.name = data.name; + if (data.country) p.country = data.country.toUpperCase(); + if (data.rank) p.rank = data.rank; + p._rankFetched = true; + })); + } + }); + } + await Promise.all(promises); + + if (!team._avgRank || team._avgRank === 0) { + if (team._roster && team._roster.length > 0) { + const validRanks = team._roster.filter(p => p.rank && p.rank > 0).map(p => p.rank); + if (validRanks.length > 0) { + team._avgRank = Math.round(validRanks.reduce((a, b) => a + b, 0) / validRanks.length); + } + } + } +} + +async function updateSlideWithAnimation() { + const container = document.getElementById('slideContainer'); + if (container) { + container.style.opacity = 0; + container.style.transition = 'opacity 0.3s ease'; + setTimeout(async () => { + await preloadMapsForTeam(seedTeams[seedCurrentTeamIndex]); + renderSlide(); + container.style.opacity = 1; + }, 300); + } else { + await preloadMapsForTeam(seedTeams[seedCurrentTeamIndex]); + renderSlide(); + } +} + +function renderSlide() { + const team = seedTeams[seedCurrentTeamIndex]; + if (!team) return; + + const teamSeedEl = document.getElementById('teamSeed'); + if (teamSeedEl) teamSeedEl.innerText = `#${team._seed}`; + + const seedWrapper = document.getElementById('seedWrapper'); + if (seedWrapper) { + seedWrapper.classList.remove('seed-gold', 'seed-silver', 'seed-bronze'); + if (team._seed === 1) seedWrapper.classList.add('seed-gold'); + else if (team._seed === 2) seedWrapper.classList.add('seed-silver'); + else if (team._seed === 3) seedWrapper.classList.add('seed-bronze'); + } + + const fullnameEl = document.getElementById('teamFullname'); + if (fullnameEl) fullnameEl.innerText = team._fullname || team._acronym || "Unknown Team"; + + const flagEl = document.getElementById('teamFlag'); + if (flagEl) { + if (team._flagName && team._flagName !== 'XX') { + flagEl.src = `https://flagcdn.com/w40/${team._flagName.toLowerCase()}.png`; + flagEl.style.display = 'block'; + } else { + flagEl.style.display = 'none'; + } + } + + const avatarEl = document.getElementById('teamAvatar'); + if (avatarEl) { + const teamName = team._fullname || team._acronym; + if (teamName) { + setImageWithFallback(avatarEl, teamName, true); + } else { + avatarEl.style.display = 'none'; + } + } + + const rankEl = document.getElementById('teamRank'); + if (rankEl) { + rankEl.innerText = `#${team._avgRank || 0}`; + document.getElementById('rankBlock').style.display = 'block'; + } + + const rosterBlock = document.getElementById('rosterBlock'); + const rosterList = document.getElementById('rosterList'); + if (rosterBlock && rosterList) { + rosterList.innerHTML = ''; + if (team._roster && team._roster.length > 0) { + rosterBlock.style.display = 'block'; + team._roster.forEach(player => { + const pDiv = document.createElement('div'); + pDiv.className = 'player-card'; + + const pAvatar = document.createElement('img'); + pAvatar.className = 'p-avatar'; + pAvatar.src = player.id ? `https://a.ppy.sh/${player.id}` : ''; + pAvatar.onerror = () => pAvatar.style.display = 'none'; + + const pInfo = document.createElement('div'); + pInfo.className = 'p-info'; + + if (player.country && player.country !== 'XX') { + const pFlag = document.createElement('img'); + pFlag.className = 'p-flag'; + pFlag.src = `https://flagcdn.com/w40/${player.country.toLowerCase()}.png`; + pInfo.appendChild(pFlag); + } + + const pName = document.createElement('span'); + pName.className = 'p-name'; + pName.innerText = player.name; + pInfo.appendChild(pName); + + pDiv.appendChild(pAvatar); + pDiv.appendChild(pInfo); + rosterList.appendChild(pDiv); + }); + } else { + rosterBlock.style.display = 'none'; + } + } + + const modsContainer = document.getElementById('modsContainer'); + if (modsContainer) { + modsContainer.innerHTML = ''; + MOD_ORDER.forEach(mod => { + const maps = team._groupedMaps[mod]; + if (!maps || maps.length === 0) return; + + const modCol = document.createElement('div'); + modCol.className = 'mod-column'; + + const modHeader = document.createElement('div'); + modHeader.className = 'mod-header'; + modHeader.innerText = mod; + let colorVar = getColorForMod(mod); + modHeader.style.color = `var(${colorVar})`; + modHeader.style.borderLeftColor = `var(${colorVar})`; + modCol.appendChild(modHeader); + + maps.forEach(m => { + const card = document.createElement('div'); + card.className = 'map-card'; + + const info = m.info || { title: `Map ID: ${m.id}`, mapper: "Unknown", diff: "Unknown", cover: null, setId: 0 }; + + let bgUrl = ''; + if (info.cover) bgUrl = info.cover; + else if (info.setId > 0) bgUrl = `https://assets.ppy.sh/beatmaps/${info.setId}/covers/cover.jpg`; + + let rankClass = ''; + if (m.seed === 1) rankClass = 'rank-gold'; + else if (m.seed === 2) rankClass = 'rank-silver'; + else if (m.seed === 3) rankClass = 'rank-bronze'; + + card.innerHTML = ` + ${bgUrl ? `
` : `
`} +
+
+ ${m.poolId || ''} +
+
+ ${info.title} [${info.diff}] +
+ ${info.mapper} +
+
+
+ #${m.seed || 0} + ${m.score ? (Math.round(m.score * 100) / 100).toLocaleString('en-US') : 0} +
+
+ `; + modCol.appendChild(card); + }); + modsContainer.appendChild(modCol); + }); + + setTimeout(() => { + document.querySelectorAll('.song-title-seed').forEach(el => { + if (el.scrollWidth > el.parentElement.clientWidth) { + el.classList.add('scroll'); + } else { + el.classList.remove('scroll'); + } + }); + }, 100); + } +} \ No newline at end of file diff --git a/counters/mitour by antoshika/metadata.txt b/counters/mitour by antoshika/metadata.txt new file mode 100644 index 00000000..f908aba1 --- /dev/null +++ b/counters/mitour by antoshika/metadata.txt @@ -0,0 +1,7 @@ +Usecase: obs-overlay +Name: mitour +Author: antoshika +Version: 1.3.9 +CompatibleWith: tosu +Resolution: 1920x1080 +authorLinks: https://osu.ppy.sh/users/18815482,https://discord.com/3jBQs9buYe \ No newline at end of file diff --git a/counters/mitour by antoshika/seed.html b/counters/mitour by antoshika/seed.html new file mode 100644 index 00000000..1bc93139 --- /dev/null +++ b/counters/mitour by antoshika/seed.html @@ -0,0 +1,57 @@ + + + + + + + + + + + +
+
+ +
+ +
+
+ +
+ SEED + #1 +
+ +
+ + +
+
+
TEAM NAME
+ +
+ +
+
+ +
+
+
+ +
+ AVG RANK + #0 +
+ +
+
+ +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/counters/mitour by antoshika/settings.json b/counters/mitour by antoshika/settings.json new file mode 100644 index 00000000..53754ec6 --- /dev/null +++ b/counters/mitour by antoshika/settings.json @@ -0,0 +1,58 @@ +[ + { + "uniqueID": "documentation", + "type": "button", + "title": "Documentation", + "description": "all about how configure overlay", + "options": "", + "value": "https://hatsunemiku39.ru/documentation" + }, + { + "uniqueID": "getApiButtonLink", + "type": "button", + "title": "Get osu!API Key", + "description": "", + "options": "", + "value": "https://osu.ppy.sh/home/account/edit#legacy-api" + }, + { + "uniqueID": "osuApiKey", + "type": "text", + "title": "osu!API Key", + "description": "Paste your API key here", + "options": "", + "value": "" + }, + { + "uniqueID": "tourneySection", + "type": "title", + "title": "Tournament Parts", + "description": "========================================================", + "options": "", + "value": "" + }, + { + "uniqueID": "mappoolData", + "type": "textarea", + "title": "Mappool", + "description": "Fill your mappool.", + "options": "", + "value": "" + }, + { + "uniqueID": "teamsData", + "type": "textarea", + "title": "Teams", + "description": "Example: Red: 18815482, 18815482 | Blue: 18815482, 18815482", + "options": "", + "value": "" + }, + { + "uniqueID": "bracketData", + "type": "textarea", + "title": "Bracket", + "description": "Paste the contents of your 'bracket.json' here", + "options": "", + "value": "" + } +] \ No newline at end of file diff --git a/counters/mitour by antoshika/showcase.html b/counters/mitour by antoshika/showcase.html new file mode 100644 index 00000000..af5be7bd --- /dev/null +++ b/counters/mitour by antoshika/showcase.html @@ -0,0 +1,63 @@ + + + + + + + + + + +
+
PICK
+
+ +
+
+
+
+ +
+ Replay by: +
+ Guest +
+
+ +
+
+
Waiting for map...
+
+
+
Artist
+
+ +
+
+
CS0
+
AR0
+
OD0
+
SR0.0★
+
BPM0
+
Length0:00
+
ID0
+
+
+
+ +
+
+
+
+
+
+
+ +
+ WARMUP +
+
+ + + + \ No newline at end of file diff --git a/counters/mitour by antoshika/style.css b/counters/mitour by antoshika/style.css new file mode 100644 index 00000000..2bd1f0c6 --- /dev/null +++ b/counters/mitour by antoshika/style.css @@ -0,0 +1,851 @@ +:root { + --red-team: #ff5e5e; + --blue-team: #5e9eff; + --bg-dark: #1a1a1a; + --text-white: #ffffff; + --text-gray: #b3b3b3; + --accent: #ffd700; + --white: #ffffff; + --font-main: 'Montserrat', sans-serif; + --font-seed: 'Manrope', sans-serif; + + --mod-nm: #5e9eff; + --mod-hd: #ffd700; + --mod-hr: #ff5e5e; + --mod-dt: #9d5eff; + --mod-fm: #2E8B57; + --mod-tb: #5F9EA0; + --mod-ap: #888888; + --mod-ez: #a5d6a7; + --mod-warmup: #000000; + --mod-def: #555555; + + --st-ranked: #76C8FF; + --st-loved: #ff66aa; + --st-qualified: #66cc66; + --st-approved: #66cc66; + --st-gray: #888888; + --st-none: #ff4444; + + --bg-color: transparent; + --text-main: #ffffff; + --text-dim: #999; +} + +body { + margin: 0; padding: 0; + width: 1920px; height: 1080px; + background-color: transparent; + font-family: var(--font-main); + color: var(--text-white); + overflow: hidden; + user-select: none; + outline: none; +} + +#app { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.top-bar { + position: absolute; + top: 30px; + left: 50%; + transform: translateX(-50%); + width: 640px; + height: 142px; + border-radius: 16px; + background: var(--bg-dark); + display: flex; + flex-direction: column; + border: 3px solid var(--active-color, var(--mod-nm)); + transition: border-color 0.5s ease; + z-index: 20; + box-shadow: 0 15px 40px rgba(0,0,0,0.4); + overflow: hidden; +} + +.card-bg { + position: absolute; top: 0; left: 0; width: 100%; height: 100%; + background-size: cover; background-position: center; + filter: blur(8px) brightness(0.4); z-index: 0; +} + +.card-content { + position: relative; z-index: 1; + display: flex; padding: 12px 15px; gap: 15px; align-items: center; + padding-right: 60px; + height: 100%; + box-sizing: border-box; +} + +.map-thumb { + width: 80px; height: 80px; border-radius: 12px; + background-size: cover; background-position: center; + box-shadow: 0 5px 15px rgba(0,0,0,0.3); background-color: #333; flex-shrink: 0; +} + +.map-details { + flex: 1; display: flex; flex-direction: column; justify-content: center; overflow: hidden; gap: 4px; + min-width: 0; +} + +.text-info { + display: flex; flex-direction: column; + width: 100%; + overflow: hidden; +} + +.title-row { + display: flex; + align-items: center; + width: 100%; + gap: 8px; +} + +.title-overflow-wrap { + flex: 0 1 auto; + min-width: 0; + overflow: hidden; + position: relative; +} + +.song-title { + font-size: 18px; font-weight: 800; white-space: nowrap; + text-shadow: 0 2px 4px rgba(0,0,0,0.5); + display: inline-block; + transform: translateX(0); +} + +.song-title.scrolling { + animation: scrollText 20s linear infinite; + padding-right: 0; +} + +@keyframes scrollText { + 0% { transform: translateX(0); } + 15% { transform: translateX(0); } + 45% { transform: translateX(calc(-1 * var(--text-width) - 50px)); opacity: 1; } + + 45.01% { transform: translateX(calc(var(--parent-width) + 50px)); opacity: 0; } + + 50% { opacity: 1; } + + 85% { transform: translateX(0); } + 100% { transform: translateX(0); } +} + +.song-artist { + font-size: 12px; color: rgba(255,255,255,0.9); font-weight: 600; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} + +.status-badge { + display: none; + width: 32px; + height: 20px; + border-radius: 10px; + border: 1.5px solid transparent; + background-color: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(4px); + background-position: center; + background-repeat: no-repeat; + background-size: 14px; + flex-shrink: 0; + box-shadow: 0 2px 5px rgba(0,0,0,0.3); + transition: all 0.3s ease; +} + +.status-badge.visible { + display: block; + animation: fadeIn 0.3s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: scale(0.8); } + to { opacity: 1; transform: scale(1); } +} + +.status-ranked { + border-color: var(--st-ranked); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2376C8FF' d='M12 2l-9.5 9.5 2.5 2.5 7-7 7 7 2.5-2.5L12 2zM12 9l-9.5 9.5 2.5 2.5 7-7 7 7 2.5-2.5L12 9z'/%3E%3C/svg%3E"); +} + +.status-loved { + border-color: var(--st-loved); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ff66aa' d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/%3E%3C/svg%3E"); +} + +.status-qualified, .status-approved { + border-color: var(--st-qualified); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366cc66' d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E"); +} + +.status-wip, .status-pending, .status-graveyard { + border-color: var(--st-gray); + background-image: url('https://i.ppy.sh/7631fe2c5bcbd32c4c2eeb0060302a5bc2bac077/68747470733a2f2f6f73752e7070792e73682f77696b692f696d616765732f7368617265642f7374617475732f70656e64696e672e706e67'); + filter: grayscale(10%) brightness(1.2); + background-size: 10px; +} + +.status-none { + border-color: var(--st-none); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ff4444' d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E"); +} + +.stats-row { + display: flex; + align-items: center; + gap: 12px; + margin-top: 2px; +} + +.stats-grid { + display: flex; gap: 10px; background: rgba(0,0,0,0.4); padding: 5px 12px; border-radius: 8px; width: fit-content; +} +.stat-item { display: flex; flex-direction: column; align-items: center; line-height: 1; } +.stat-item .label { font-size: 9px; font-weight: 700; color: var(--text-gray); margin-bottom: 2px; text-transform: uppercase; } +.stat-item .val { font-size: 12px; font-weight: 700; } +.stat-item.highlight .val { color: var(--accent); } + +.pick-bar { + position: relative; + z-index: 2; + width: 100%; + height: 38px; + background: var(--active-color, var(--mod-nm)); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-weight: 900; + font-size: 18px; + letter-spacing: 2px; + margin-top: auto; + padding: 0 20px; + box-sizing: border-box; + text-shadow: 0 2px 4px rgba(0,0,0,0.6); + + border-radius: 0 0 13px 13px; + + box-shadow: none; +} + +.pick-bar span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} + +.inner-pick-badge { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 0; + background: #000; + display: flex; + align-items: center; + justify-content: center; + writing-mode: vertical-rl; + text-orientation: mixed; + color: #fff; + font-weight: 900; + letter-spacing: 3px; + font-size: 16px; + z-index: 30; + transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: -5px 0 20px rgba(0,0,0,0.5); + overflow: hidden; +} +.inner-pick-badge.visible { width: 50px; } +.inner-pick-badge.red { background: var(--red-team); } +.inner-pick-badge.blue { background: var(--blue-team); } + +.bottom-bar { + position: absolute; bottom: 40px; left: 50%; transform: translateX(-50%); + display: flex; align-items: center; gap: 15px; width: 100%; justify-content: center; z-index: 10; +} +.team { + display: flex; align-items: center; background: rgba(20, 20, 20, 0.85); + backdrop-filter: blur(10px); padding: 5px 40px; border-radius: 16px; + box-shadow: 0 10px 30px rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.05); + height: 70px; min-width: 400px; transition: all 0.3s ease; +} +.team.leading { + background: rgba(40, 40, 40, 0.95); box-shadow: 0 0 25px rgba(255, 255, 255, 0.1); + border-color: rgba(255,255,255,0.3); transform: scale(1.02); z-index: 5; +} +.team.red { border-bottom: 3px solid var(--red-team); justify-content: flex-end; } +.team.blue { border-bottom: 3px solid var(--blue-team); justify-content: flex-start; } +.team.losing { opacity: 0.8; transform: scale(0.98); } + +.team-info-left, .team-info-right { display: flex; flex-direction: column; justify-content: center; gap: 4px; } +.team-info-left { align-items: flex-end; } +.team-info-right { align-items: flex-start; } + +.team-name { font-size: 20px; font-weight: 700; text-transform: uppercase; margin: 0; } +.team-icon { width: 50px; height: 50px; border-radius: 8px; background-size: cover; background-position: center; box-shadow: 0 4px 10px rgba(0,0,0,0.3); margin: 0 15px; background-color: transparent; } + +.score-wrapper { display: flex; flex-direction: column; align-items: center; justify-content: center; min-width: 120px; height: 100%; } + +.score { font-size: 38px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; margin-bottom: 0; } +.red .score { color: var(--red-team); } +.blue .score { color: var(--blue-team); } + +.diff-val { + font-size: 14px; + font-weight: 700; + color: var(--text-gray); + height: 0; + overflow: hidden; + transition: all 0.3s ease; + margin-top: 0; + opacity: 0; +} +.diff-val.visible { height: 16px; opacity: 0.9; margin-top: 2px; } + +.vs-marker { + font-weight: 800; font-size: 20px; font-style: italic; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(5px); + color: #fff; + width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; + border-radius: 50%; box-shadow: 0 5px 15px rgba(0,0,0,0.3); flex-shrink: 0; +} +.stars { display: flex; gap: 5px; } +.star-point { width: 22px; height: 10px; background: rgba(255,255,255,0.15); border-radius: 5px; transition: all 0.3s ease; } +.star-point.active { background: #fff; box-shadow: 0 0 8px white; transform: scale(1.1); } + +#mappool-overlay { + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + background: rgba(5, 5, 5, 0.96); z-index: 100; + display: flex; flex-direction: column; align-items: center; justify-content: center; + opacity: 0; pointer-events: none; transition: opacity 0.4s ease; + padding: 40px 80px; box-sizing: border-box; + overflow-y: auto; +} +#mappool-overlay.visible { opacity: 1; pointer-events: all; } + +.pool-header { + font-size: 24px; font-weight: 900; letter-spacing: 4px; + background: #fff; color: #000; padding: 5px 20px; + border-radius: 12px; margin-bottom: 30px; text-transform: uppercase; + flex-shrink: 0; +} + +.pool-grid { + display: flex; flex-direction: column; align-items: center; + gap: 15px; width: 100%; max-width: 1700px; +} + +.pool-row { + display: flex; flex-wrap: wrap; justify-content: center; + gap: 15px; width: 100%; +} + +.pool-card { + position: relative; + width: 440px; height: 75px; background: #111; + overflow: hidden; display: flex; align-items: center; cursor: pointer; + border: 2px solid rgba(255,255,255,0.05); border-radius: 10px; + box-shadow: 0 4px 10px rgba(0,0,0,0.5); + transition: transform 0.2s, border-color 0.2s; +} +.pool-card:hover { transform: translateY(-3px); border-color: rgba(255,255,255,0.3); } + +.pool-card-bg { + position: absolute; top: 0; left: 0; width: 100%; height: 100%; + background-size: cover; background-position: center; opacity: 0.8; transition: opacity 0.3s; +} +.pool-card-gradient { + position: absolute; top: 0; left: 0; width: 100%; height: 100%; + background: linear-gradient(90deg, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.85) 60%, rgba(0,0,0,0.2) 100%); + z-index: 1; +} + +.pool-card-content { + position: relative; z-index: 2; display: flex; justify-content: space-between; align-items: center; + width: 100%; height: 100%; padding-left: 20px; +} +.song-info-block { display: flex; flex-direction: column; justify-content: center; max-width: 330px; } + +.pc-title { + font-size: 15px; font-weight: 800; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + text-shadow: 0 2px 4px rgba(0,0,0,0.8); line-height: 1.2; +} +.pc-artist { + font-size: 12px; font-weight: 600; color: #ccc; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 3px; +} +.pc-mapper { font-size: 11px; color: #888; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%; display: block; } +.pc-mapper span { color: #aaa; font-weight: 700; margin-right: 5px; } + +.mod-tag { + height: 100%; width: 60px; display: flex; align-items: center; justify-content: center; + font-size: 20px; font-weight: 900; color: #fff; + text-shadow: 0 2px 4px rgba(0,0,0,0.5); box-shadow: -5px 0 15px rgba(0,0,0,0.3); +} + +.status-overlay { + position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; + background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; + opacity: 0; transition: 0.3s; + font-size: 26px; font-weight: 900; letter-spacing: 3px; text-transform: uppercase; + backdrop-filter: blur(2px); border-radius: 10px; + pointer-events: none; +} + +.pool-card.picked-red .status-overlay { opacity: 1; background: rgba(255, 94, 94, 0.5); content: "PICK"; } +.pool-card.picked-red .status-overlay::after { content: "PICK"; color: #fff; } +.pool-card.picked-red { border-color: var(--red-team); } + +.pool-card.picked-blue .status-overlay { opacity: 1; background: rgba(94, 158, 255, 0.5); } +.pool-card.picked-blue .status-overlay::after { content: "PICK"; color: #fff; } +.pool-card.picked-blue { border-color: var(--blue-team); } + +.pool-card.banned-red .status-overlay { opacity: 1; background: rgba(255, 94, 94, 0.2); } +.pool-card.banned-red .status-overlay::after { content: "BAN"; color: var(--red-team); text-decoration: line-through; } +.pool-card.banned-red { opacity: 0.5; } + +.pool-card.banned-blue .status-overlay { opacity: 1; background: rgba(94, 158, 255, 0.2); } +.pool-card.banned-blue .status-overlay::after { content: "BAN"; color: var(--blue-team); text-decoration: line-through; } +.pool-card.banned-blue { opacity: 0.5; } + +.pool-card.protect-red { box-shadow: 0 0 15px var(--red-team); border-color: var(--red-team); } +.pool-card.protect-red .status-overlay { opacity: 1; background: rgba(0, 0, 0, 0); backdrop-filter: none; } +.pool-card.protect-red .status-overlay::after { content: "PROTECT"; color: #74c274; text-shadow: 0 0 10px rgba(116, 194, 116, 0.2); } + +.pool-card.protect-blue { box-shadow: 0 0 15px var(--blue-team); border-color: var(--blue-team); } +.pool-card.protect-blue .status-overlay { opacity: 1; background: rgba(0, 0, 0, 0); backdrop-filter: none; } +.pool-card.protect-blue .status-overlay::after { content: "PROTECT"; color: #74c274; text-shadow: 0 0 10px rgba(116, 194, 116, 0.2); } + +.progress-container { + margin-top: 6px; + width: 100%; +} + +.progress-bar-bg { + width: 100%; height: 4px; + background: rgba(255,255,255,0.15); + border-radius: 2px; + overflow: hidden; + margin-top: 4px; +} + +.progress-fill { + height: 100%; width: 0%; + background: var(--text-white); + transition: width 0.1s linear; + box-shadow: 0 0 10px rgba(255,255,255,0.5); +} + +.chat-overlay { + position: absolute; + top: -95px; + right: 210px; + width: 380px; + height: 300px; + background: rgba(5, 5, 5, 0.95); + backdrop-filter: blur(10px); + border: 2px solid rgb(53 53 53); + border-top: none; + border-radius: 0 0 16px 16px; + z-index: 200; + padding: 10px 15px; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: flex-end; + box-shadow: 0 5px 20px rgba(0,0,0,0.6); + mask-image: linear-gradient(to bottom, transparent 0%, black 10%); + -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 10%); + transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.6s ease; +} + +.chat-overlay.hidden { + transform: translateY(-120%); + opacity: 0; +} + +.chat-messages { + display: flex; + flex-direction: column; + gap: 4px; + overflow-y: hidden; + justify-content: flex-end; +} + +.chat-irc-line { + font-size: 13px; + font-weight: 600; + line-height: 1.4; + color: #fff; + text-shadow: 0 1px 2px rgba(0,0,0,0.9); + word-wrap: break-word; +} + +.time-stamp { + color: #888; + font-size: 11px; + margin-right: 6px; + font-family: monospace; +} + +.user-name { + font-weight: 800; + margin-right: 4px; +} + +.user-name.team-left { color: var(--red-team); } +.user-name.team-right { color: var(--blue-team); } +.user-name.team-unknown { color: var(--text-gray); } + +.user-name.role-referee { + background-color: #ffd700; + color: #000 !important; + border-radius: 6px; + padding: 0 5px; + margin-right: 6px; + display: inline-block; + vertical-align: middle; + line-height: 1.2; + font-weight: 800; +} + +.user-name.role-banchobot { + background-color: #9d5eff; + color: #000 !important; + border-radius: 6px; + padding: 0 5px; + margin-right: 6px; + display: inline-block; + vertical-align: middle; + line-height: 1.2; + font-weight: 800; +} + +.msg-content { + color: #eee; +} + +.background-layer { + position: absolute; + top: 0; left: 0; width: 100%; height: 100%; + background: radial-gradient(circle at 30% 50%, rgba(80, 0, 0, 0.4), transparent 70%); + z-index: 0; +} + +.hidden { + opacity: 0; + pointer-events: none; + transform: translateY(20px); + transition: all 0.5s ease; +} + +#winner-container { + display: flex; + width: 1600px; + height: 800px; + z-index: 1; + opacity: 1; + transform: translateY(0); + transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1); +} + +.left-panel { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.avatar-ring { + position: relative; + width: 450px; + height: 450px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.avatar-glow { + position: absolute; + width: 100%; height: 100%; + border-radius: 50%; + border: 10px solid var(--red-team); + box-shadow: 0 0 50px var(--red-team), inset 0 0 30px var(--red-team); + animation: rotateGlow 10s linear infinite; + opacity: 0.8; +} + +.winner-blue .avatar-glow { + border-color: var(--blue-team); + box-shadow: 0 0 50px var(--blue-team), inset 0 0 30px var(--blue-team); +} + +.team-logo { + width: 380px; + height: 380px; + border-radius: 50%; + background-size: cover; + background-position: center; + background-color: #000; + z-index: 2; + border: none; +} + +.right-panel { + flex: 1.2; + display: flex; + flex-direction: column; + justify-content: center; + padding-left: 50px; +} + +.top-meta { + display: none; +} + +.winner-title { + font-size: 130px; + font-weight: 900; + line-height: 1; + letter-spacing: 4px; + margin-bottom: 40px; + text-transform: uppercase; + text-shadow: 0 5px 15px rgba(0,0,0,0.5); +} + +.team-info-block { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 5px; + margin-bottom: 40px; +} + +.team-label { + background: var(--red-team); + padding: 5px 15px; + font-size: 20px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 1px; +} + +.winner-blue .team-label { background: var(--blue-team); } + +.team-name-box { + background: #fff; + color: #000; + padding: 10px 30px; + font-size: 60px; + font-weight: 800; + box-shadow: 0 10px 30px rgba(0,0,0,0.3); + min-width: 300px; +} + +.players-grid { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 10px; +} + +.player-item { + font-size: 24px; + font-weight: 500; + color: #eee; + display: flex; + align-items: center; + gap: 15px; + text-shadow: -2px 2px 0px rgba(0,0,0,1); +} + +.player-flag { + width: auto; + height: 25px; + border-radius: 3px; + box-shadow: 0 2px 4px rgba(0,0,0,0.3); + flex-shrink: 0; +} + +@keyframes rotateGlow { + 0% { transform: rotate(0deg) scale(1); } + 50% { transform: rotate(180deg) scale(1.02); } + 100% { transform: rotate(360deg) scale(1); } +} + +.seed-hidden { display: none !important; } + +.click-zone { position: absolute; top: 0; bottom: 0; width: 30%; z-index: 99999; cursor: pointer; pointer-events: auto; } +.zone-left { left: 0; } +.zone-right { right: 0; } + +@keyframes slideIn { + from { opacity: 0; transform: translateX(50px); } + to { opacity: 1; transform: translateX(0); } +} + +.slide-container { + display: flex; width: 100%; height: 100%; + padding: 60px; box-sizing: border-box; gap: 40px; + animation: slideIn 0.5s cubic-bezier(0.16, 1, 0.3, 1); + font-family: var(--font-seed); +} + +.left-panel-seed { + width: 450px; height: 100%; + display: flex; flex-direction: column; justify-content: center; + border-right: 1px solid rgba(255,255,255,0.1); padding-right: 30px; +} +.left-content-wrapper { display: flex; flex-direction: column; gap: 30px; } + +.seed-header { border-left: 6px solid transparent; padding-left: 20px; } +.seed-label { display: block; font-size: 1.2rem; font-weight: 700; letter-spacing: 4px; color: var(--text-dim); margin-bottom: -5px; } +#teamSeed { font-size: 8rem; font-weight: 800; line-height: 0.9; color: #fff; } +.seed-gold { border-left-color: #ffd700; } .seed-gold #teamSeed { color: #ffd700; text-shadow: 0 0 40px rgba(255, 215, 0, 0.4); } +.seed-silver { border-left-color: #e0e0e0; } .seed-silver #teamSeed { color: #e0e0e0; text-shadow: 0 0 40px rgba(255, 255, 255, 0.4); } +.seed-bronze { border-left-color: #cd7f32; } .seed-bronze #teamSeed { color: #cd7f32; text-shadow: 0 0 40px rgba(205, 127, 50, 0.4); } + +.team-block { display: flex; flex-direction: column; align-items: flex-start; } +.team-avatar { + height: 100px; width: 100px; object-fit: cover; + border-radius: 16px; margin-bottom: 20px; + box-shadow: 0 5px 20px rgba(0,0,0,0.6); + background: #222; transition: opacity 0.3s; +} +.team-info-wrapper { display: flex; flex-direction: column; } +.name-flag-row { display: flex; align-items: center; gap: 15px; flex-wrap: wrap; } +.team-fullname { font-size: 3rem; color: #fff; font-weight: 800; text-transform: uppercase; line-height: 1; margin-bottom: 2px; word-break: break-word; } +.team-flag { height: 35px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.5); margin-top: 4px; } +.team-acronym { font-size: 1.4rem; font-weight: 600; color: var(--text-dim); letter-spacing: 2px; margin-top: 5px; } + +.roster-block { display: none; margin-top: 10px; } +.roster-list { display: flex; flex-direction: row; flex-wrap: wrap; gap: 20px; align-items: flex-start; } + +.player-card { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 90px; text-align: center; } +.p-avatar { + width: 64px; height: 64px; border-radius: 12px; + object-fit: cover; border: 2px solid rgba(255,255,255,0.15); + background: #1a1a1a; box-shadow: 0 4px 8px rgba(0,0,0,0.5); +} +.p-info { display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%; } +.p-name { font-size: 0.9rem; font-weight: 700; color: #eee; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 65px; } +.p-flag { width: 18px; border-radius: 2px; box-shadow: 0 1px 3px rgba(0,0,0,0.5); display: block; } + +.rank-block { display: none; margin-top: auto; } +.stat-label { font-size: 0.9rem; color: var(--text-dim); letter-spacing: 2px; font-weight: 700; text-transform: uppercase; margin-bottom: 5px; } +.stat-value { font-size: 2.5rem; font-weight: 700; display: block; } + +.right-panel-seed { flex: 1; display: flex; align-items: center; justify-content: center; height: 100%; } +.mods-container { display: flex; flex-direction: row; gap: 20px; flex-wrap: wrap; align-items: flex-start; justify-content: center; align-content: center; width: 100%; max-height: 98vh; } +.mod-column { + display: flex; flex-direction: column; gap: 10px; width: 270px; + background: rgba(0, 0, 0, 0.4); backdrop-filter: blur(10px); + padding: 12px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.05); +} +.mod-header { font-size: 1.4rem; font-weight: 900; padding-left: 10px; border-left: 4px solid #fff; line-height: 1; text-transform: uppercase; margin-bottom: 5px; opacity: 0.9; } + +.map-card { + height: 95px; background: #151515; border-radius: 8px; position: relative; overflow: hidden; + border: 1px solid rgba(255,255,255,0.1); display: flex; flex-direction: column; + box-shadow: 0 4px 8px rgba(0,0,0,0.4); +} +.map-bg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-size: cover; background-position: center; opacity: 0.3; } +.map-bg-fallback { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0.15; } +.map-content { position: relative; z-index: 2; padding: 6px 10px; height: 100%; display: flex; flex-direction: column; justify-content: space-between; } +.map-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; } +.pool-id { font-size: 1.1rem; font-weight: 900; font-style: italic; line-height: 1; margin-right: 5px; } + +.map-info { display: flex; flex-direction: column; overflow: hidden; width: 100%; } +.marquee-container { + width: 100%; overflow: hidden; white-space: nowrap; position: relative; +} +.song-title-seed { font-size: 0.8rem; font-weight: 700; color: #fff; display: inline-block; white-space: nowrap; } + +.song-title-seed.scroll { + animation: scroll-text 10s linear infinite; + padding-right: 20px; +} + +@keyframes scroll-text { + 0%, 15% { transform: translateX(0); } + 70% { transform: translateX(calc(-100% + 220px)); } + 85% { transform: translateX(calc(-100% + 220px)); } + 100% { transform: translateX(0); } +} + +.map-mapper { font-size: 0.65rem; color: #ccc; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } +.map-bot { display: flex; justify-content: space-between; align-items: flex-end; } +.map-rank { font-size: 1.2rem; font-weight: 800; } +.rank-gold { color: #ffd700; text-shadow: 0 0 5px rgba(255,215,0,0.5); } +.rank-silver { color: #e0e0e0; text-shadow: 0 0 5px rgba(255,255,255,0.5); } +.rank-bronze { color: #cd7f32; text-shadow: 0 0 5px rgba(205,127,50,0.5); } +.map-score { font-size: 1.2rem; font-weight: 700; line-height: 1; } + +.showcase-mode { + display: flex; + align-items: center; + justify-content: center; + background-color: transparent; + width: 1920px; + height: 1080px; + margin: 0; +} + +.showcase-mode .top-bar { + position: relative; + top: auto; + left: auto; + transform: scale(1.3); + margin: 0; + border-radius: 9px; !important + border: 3px solid var(--active-color, var(--mod-nm)); !important + box-shadow: 0 10px 30px rgba(0,0,0,0.5); + overflow: visible; +} + +.replay-row { + display: flex; + align-items: baseline; + gap: 6px; + margin-bottom: 2px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding-bottom: 2px; +} + +.replay-prefix { + font-size: 10px; + font-weight: 700; + color: #999; + text-transform: uppercase; +} + +.replay-name-wrapper { + flex-grow: 1; + overflow: hidden; + height: 20px; + display: flex; + align-items: center; +} + +.replay-name { + font-family: var(--font-main); + font-size: 16px; + font-weight: 700; + color: var(--text-white); + white-space: nowrap; + text-shadow: 0 1px 2px rgba(0,0,0,0.5); + transform-origin: left center; + transition: opacity 0.3s ease; +} +.replay-name.hidden { opacity: 0; } \ No newline at end of file diff --git a/counters/mitour by antoshika/winner.html b/counters/mitour by antoshika/winner.html new file mode 100644 index 00000000..e55c9550 --- /dev/null +++ b/counters/mitour by antoshika/winner.html @@ -0,0 +1,38 @@ + + + + + + + + + +
+
+ + +
+ + + \ No newline at end of file