diff --git a/bst@asphyxia/README.md b/bst@asphyxia/README.md index 382da31..c4ffc86 100644 --- a/bst@asphyxia/README.md +++ b/bst@asphyxia/README.md @@ -1,8 +1,9 @@ # BeatStream -Plugin Version: **v0.1.0-beta** +Plugin Version: **v1.0.0** Supported Versions: - BeatStream アニムトライヴ - - Back end ✔ \ No newline at end of file + - Back end ✔ + - Web UI ✔ \ No newline at end of file diff --git a/bst@asphyxia/handlers/bst2/common.ts b/bst@asphyxia/handlers/bst2/common.ts index 710563f..8039168 100644 --- a/bst@asphyxia/handlers/bst2/common.ts +++ b/bst@asphyxia/handlers/bst2/common.ts @@ -147,7 +147,7 @@ export namespace Bst2HandlersCommon { let query: Query = { collection: "bst.bst2.playData.musicRecord#userId", userId: stageLog.userId, musicId: stageLog.musicId, chart: stageLog.chart } let oldRecord = await DB.FindOne(query) - let time = Date.now() / 1000 + let time = Date.now() stageLog.time = time stageLog.isCourseStage = isCourseStage @@ -199,7 +199,7 @@ export namespace Bst2HandlersCommon { let query: Query = { collection: "bst.bst2.playData.course#userId", userId: courseLog.userId, courseId: courseLog.courseId } let oldRecord = await DB.FindOne(query) - let time = Date.now() / 1000 + let time = Date.now() courseLog.time = time if (oldRecord == null) { diff --git a/bst@asphyxia/handlers/bst2/webui.ts b/bst@asphyxia/handlers/bst2/webui.ts new file mode 100644 index 0000000..85e6ffd --- /dev/null +++ b/bst@asphyxia/handlers/bst2/webui.ts @@ -0,0 +1,39 @@ +import { IBst2Base, IBst2Customization } from "../../models/bst2/profile" +import { WebUIMessageType } from "../../models/utility/webui_message" +import { DBM } from "../utility/db_manager" +import { UtilityHandlersWebUI } from "../utility/webui" + +export namespace Bst2HandlersWebUI { + export const UpdateSettings = async (data: { + refid: string + name: string + rippleNote: number + sfxNormalNote: number + sfxRippleNote: number + sfxSlashNote: number + sfxStreamNote: number + backgroundBrightness: number + judgeText: number + rippleNoteGuide: number + streamNoteGuide: number + sfxFine: number + sfxStreamNoteTail: number + }) => { + try { + let customization = await DB.FindOne(data.refid, { collection: "bst.bst2.player.customization" }) + if (customization == null) throw new Error("No profile for refid=" + data.refid) + customization.custom[0] = data.rippleNote + customization.custom[2] = data.sfxNormalNote + customization.custom[3] = data.sfxRippleNote + customization.custom[4] = data.sfxSlashNote + customization.custom[5] = data.sfxStreamNote + customization.custom[6] = data.backgroundBrightness + customization.custom[7] = (data.judgeText << 0) | (data.rippleNoteGuide << 1) | (data.streamNoteGuide << 2) | (data.sfxStreamNoteTail << 3) | (data.sfxFine << 4) + customization.custom[9] = data.judgeText + DBM.update(data.refid, { collection: "bst.bst2.player.customization" }, customization) + UtilityHandlersWebUI.pushMessage("Save BeatStream Animtribe settings succeeded!", 2, WebUIMessageType.success, data.refid) + } catch (e) { + UtilityHandlersWebUI.pushMessage("Error while save BeatStream Animtribe settings: " + e.message, 2, WebUIMessageType.error, data.refid) + } + } +} \ No newline at end of file diff --git a/bst@asphyxia/handlers/utility/db_manager.ts b/bst@asphyxia/handlers/utility/db_manager.ts index 35a26b6..b4e5a1b 100644 --- a/bst@asphyxia/handlers/utility/db_manager.ts +++ b/bst@asphyxia/handlers/utility/db_manager.ts @@ -121,6 +121,7 @@ export namespace DBM { } async function checkData>(data: T): Promise { + for (let k in data) if (k.startsWith("__")) delete data[k] if (await DB.FindOne({ collection: "dbManager.collectionName", name: data.collection }) == null) { await DB.Insert({ collection: "dbManager.collectionName", name: data.collection }) } @@ -166,7 +167,7 @@ export namespace DBM { break } } catch (e) { - await log(Date.now().toLocaleString() + " Error: " + (e as Error).message) + await log(new Date().toLocaleString() + " Error: " + (e as Error).message) } } return result diff --git a/bst@asphyxia/handlers/utility/webui.ts b/bst@asphyxia/handlers/utility/webui.ts index 2c24cf2..ba51199 100644 --- a/bst@asphyxia/handlers/utility/webui.ts +++ b/bst@asphyxia/handlers/utility/webui.ts @@ -7,6 +7,6 @@ export namespace UtilityHandlersWebUI { } export const removeWebUIMessage = async () => { - await DB.Remove({ collection: "utility.webuiMessage" }) + await DBM.remove(null, { collection: "utility.webuiMessage" }) } } \ No newline at end of file diff --git a/bst@asphyxia/index.ts b/bst@asphyxia/index.ts index 6692759..6d5d994 100644 --- a/bst@asphyxia/index.ts +++ b/bst@asphyxia/index.ts @@ -2,18 +2,21 @@ import { UtilityHandlersCommon } from "./handlers/utility/common" import { UtilityHandlersWebUI } from "./handlers/utility/webui" import { initialize } from "./handlers/utility/initialize" import { Bst2HandlersCommon } from "./handlers/bst2/common" +import { Bst2HandlersWebUI } from "./handlers/bst2/webui" export function register() { R.GameCode("NBT") - RouteBst2() + routeBst2() + + R.WebUIEvent("removeWebUIMessage", UtilityHandlersWebUI.removeWebUIMessage) R.Unhandled() initialize() } -function RouteBst2() { +function routeBst2() { R.Route("info2.common", Bst2HandlersCommon.Common) R.Route("pcb2.boot", Bst2HandlersCommon.BootPcb) R.Route("player2.start", Bst2HandlersCommon.StartPlayer) @@ -24,4 +27,6 @@ function RouteBst2() { R.Route("player2.stagedata_write", Bst2HandlersCommon.WriteStageLog) R.Route("player2.course_stage_data_write", Bst2HandlersCommon.WriteCourseStageLog) R.Route("player2.course_data_write", Bst2HandlersCommon.WriteCourseLog) + + R.WebUIEvent("bst2UpdateSettings", Bst2HandlersWebUI.UpdateSettings) } \ No newline at end of file diff --git a/bst@asphyxia/models/bst2/profile.ts b/bst@asphyxia/models/bst2/profile.ts index 74b0b01..13103cb 100644 --- a/bst@asphyxia/models/bst2/profile.ts +++ b/bst@asphyxia/models/bst2/profile.ts @@ -107,6 +107,7 @@ export const Bst2UnlockingInfoMap: KM = { } export interface IBst2Customization extends ICollection<"bst.bst2.player.customization"> { + // [rippleNote, rippleNoteColor, sfxNormalNote, sfxRippleNote, sfxSlashNote, sfxStreamNote, backgroundBrightnessTimes2, (000{sfxFine}{sfxStreamTail}{streamNoteGuide}{rippleNoteGuide}{judgeText}, ?, ?, ?, ?, ?, ?, ?, ?)] custom: FixedSizeArray } export const Bst2CustomizationMap: KM = { diff --git a/bst@asphyxia/utility/about.ts b/bst@asphyxia/utility/about.ts index 6b3b4ae..2221fdd 100644 --- a/bst@asphyxia/utility/about.ts +++ b/bst@asphyxia/utility/about.ts @@ -1,3 +1,4 @@ export type Game = "bst" export const game: Game = "bst" -export const version: string = "0.1.0" +export type PluginVersion = "1.0.0" +export const version: PluginVersion = "1.0.0" diff --git a/bst@asphyxia/webui/css/webui_util.css b/bst@asphyxia/webui/css/webui_util.css new file mode 100644 index 0000000..5bfec78 --- /dev/null +++ b/bst@asphyxia/webui/css/webui_util.css @@ -0,0 +1,243 @@ +#tab-content, .tab-content { + display: none; +} + +#tab-content.is-active, .tab-content.is-active { + display: block; +} + +tr#tab-content.is-active, tr.tab-content.is-active { + display: table-row; +} + +#tabs li.disabled a { + background-color: #c0c0c0; + border-color: #c0c0c0; + color: #7f7f7f; + cursor: default; +} + +#form-pagination ul.pagination-list { + margin: 0!important; +} +.pagination-link, .pagination-next, .pagination-previous { + border-color: transparent; + transition: .2s linear; +} +.pagination-next, .pagination-previous { + color: #209CEE; +} +.pagination-next:not([disabled]):hover, .pagination-previous:not([disabled]):hover { + color: #118fe4; +} +/* Set all link color to Asphyxia CORE blue */ +::selection { + color: white; + background-color: #209CEE; +} +a { + color: #209CEE; +} +.tabs.is-toggle li.is-active a { + background-color: #209CEE; + border-color: #209CEE; +} +.tabs li.is-active a { + color: #209CEE; + border-color: #209CEE; +} +.pagination-link.is-current { + background-color: #209CEE; + border-color: #209CEE; + cursor: default; +} +.select:not(.is-multiple):not(.is-loading):after { + border-color: #209CEE; +} +.select select:active, .select select:focus { + border-color: #209CEE; +} +.button.is-link { + background-color: #209CEE; +} +.button.is-link.is-active, .button.is-link:active, .button.is-link.is-hovered, .button.is-link:hover { + background-color: #118fe4; +} +.input:active, .input:focus { + border-color: #209CEE; +} +.table tr.is-selected { + background-color: #209CEE; +} + +#card-content.is-hidden { + display: none; +} +#card-content { + display: block; +} +.marquee-label { + display: inline-block; +} +.marquee-label-container { + overflow-x: hidden; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; +} + +/* from Bulma */ +.button.is-danger.is-light { + background-color: #feecf0; + color: #cc0f35; +} +.button.is-link.is-light { + background-color: #edf8ff; + color: #209CEE; +} +.button.is-danger.is-light.is-hovered, .button.is-danger.is-light:hover { + background-color: #fde0e6; + color: #cc0f35; +} +.button.is-link.is-light.is-hovered, .button.is-link.is-light:hover { + background-color: #e0f1fc; + color: #209CEE; +} +.tag.is-link.is-light { + background-color: #edf8ff; + color: #0D7DC6; +} +.tag.is-link.is-light:hover { + background-color: #209CEE; + color: white; +} +.tag.is-delete:hover { + background-color: #FF3860!important; + color: white; +} + +@media screen and (max-width: 768px) { + .pagination { + flex-wrap: nowrap; + justify-content: left; + } +} +.pagination-list { + flex-wrap: nowrap; + list-style: none!important; + margin-top: 0.25em!important; + margin-bottom: 0.25em!important; +} + +.content li + li { + margin-top: 0; +} + +.one-quarter#forwide, .one-third#forwide { + display: block; + min-width: 100px; +} +.one-quarter#fornarrow, .one-third#fornarrow { + display: none; + min-width: 50px; +} +@media only screen and (max-width: 1023px) { + .one-quarter#forwide { + display: none; + } + .one-quarter#fornarrow { + display: block; + } +} +@media only screen and (max-width: 700px) { + .one-third#forwide { + display: none; + } + .one-third#fornarrow { + display: block; + } +} + + +@keyframes notification-fadeout { + 0% { + opacity: 1; + display: block; + } + 80% { + opacity: 1; + display: block; + } + 99.99% { + opacity: 0; + display: block; + } + 100% { + opacity: 0; + display: none; + } +} + +.notification { + animation: notification-fadeout 8s forwards; + animation-play-state: paused; +} +.notification:hover { + animation-play-state: paused; +} +.modal { + padding-bottom: 13px; +} +@media screen and (max-width:1024px) { + .modal { + transition: padding-left .2s ease-in-out 50ms; + } +} +@media screen and (min-width:1023px) { + .modal { + padding-left: 256px; + transition: padding-left .2s ease-in-out 50ms; + } +} +.tag { + transition: linear .2s; +} +.tags .tag:not(:last-child) { + cursor: default; +} + +.modal table tr { + border: solid #dbdbdb; + border-width: 0 0 1px; +} +.modal table tbody tr:last-child { + border-bottom-width: 0; +} +.hidden-wrapper { + overflow: hidden; +} +.hidden-x-wrapper { + overflow-x: hidden; +} +.hidden-y-wrapper { + overflow-y: hidden; +} +.scrolling-wrapper { + overflow: auto; +} +.scrolling-x-wrapper { + overflow-x: auto; +} +.scrolling-y-wrapper { + overflow-y: auto; +} +a.pagination-previous { + overflow: hidden; +} +a.pagination-next { + overflow: hidden; +} +.button.checkbox, .button.checkbox .checkmark { + transition: linear .2s; +} diff --git a/bst@asphyxia/webui/js/webui_util.js b/bst@asphyxia/webui/js/webui_util.js new file mode 100644 index 0000000..33ef6f5 --- /dev/null +++ b/bst@asphyxia/webui/js/webui_util.js @@ -0,0 +1,618 @@ +function initializePaginatedContent() { + let containers = document.querySelectorAll(".paginated-container") + + for (let container of containers) { + let pageSizeInput = container.querySelector("input.page-size") + let paginations = container.querySelectorAll(".pagination") + let contents = container.querySelectorAll(".paginated-content") + let group = container.getAttribute("pagination-group") + let flags = { isFirst: true } + let refreshEllipsis = (param) => { + if (flags.isFirst) return + let maxWidth = container.offsetWidth / 2 + for (let pagination of paginations) { + let buttons = pagination.querySelector("ul.pagination-list") + if (buttons.childElementCount == 0) return + let show = (index) => buttons.querySelector("li[tab-index=\"" + index + "\"]").style.display = "block" + let hide = (index) => buttons.querySelector("li[tab-index=\"" + index + "\"]").style.display = "none" + let previousButton = pagination.querySelector("a.pagination-previous") + let nextButton = pagination.querySelector("a.pagination-next") + let leftEllipsis = buttons.querySelector("li.ellipsis-left") + let rightEllipsis = buttons.querySelector("li.ellipsis-right") + let width = buttons.firstChild.offsetWidth.toString() + leftEllipsis.style.width = width + "px" + rightEllipsis.style.width = width + "px" + let count = buttons.childElementCount - 2 + let maxButtonCount = Math.max((buttons.firstChild.offsetWidth == 0) ? 5 : Math.trunc(maxWidth / buttons.firstChild.offsetWidth), 5) + let current = (param instanceof HTMLElement) ? param : buttons.querySelector("li.is-active") + let index = parseInt((current == null) ? 0 : current.getAttribute("tab-index")) + if (index == 0) previousButton.setAttribute("disabled", "") + else previousButton.removeAttribute("disabled") + if (index == (count - 1)) nextButton.setAttribute("disabled", "") + else nextButton.removeAttribute("disabled") + if (count <= maxButtonCount) { + for (let i = 0; i < count; i++) buttons.querySelector("li[tab-index=\"" + i + "\"]").style.display = "block" + leftEllipsis.style.display = "none" + rightEllipsis.style.display = "none" + } else { + maxButtonCount = Math.trunc((maxButtonCount - 1) / 2) * 2 + 1 + let maxSurroundingButtonCount = (maxButtonCount - 5) / 2 + let maxNoEllipsisIndex = maxButtonCount - 2 - maxSurroundingButtonCount - 1 + + if (index <= maxNoEllipsisIndex) { + for (let i = 0; i <= (maxNoEllipsisIndex + maxSurroundingButtonCount); i++) show(i) + for (let i = (maxNoEllipsisIndex + maxSurroundingButtonCount) + 1; i < count - 1; i++) hide(i) + show(count - 1) + leftEllipsis.style.display = "none" + rightEllipsis.style.display = "block" + } else if (index >= (count - maxNoEllipsisIndex - 1)) { + for (let i = 1; i < (count - maxNoEllipsisIndex - maxSurroundingButtonCount - 1); i++) hide(i) + for (let i = (count - maxNoEllipsisIndex - maxSurroundingButtonCount - 1); i < count; i++) show(i) + show(0) + leftEllipsis.style.display = "block" + rightEllipsis.style.display = "none" + } else { + for (let i = 1; i < (index - maxSurroundingButtonCount); i++) hide(i) + for (let i = (index - maxSurroundingButtonCount); i <= (index + maxSurroundingButtonCount); i++) show(i) + for (let i = (index + maxSurroundingButtonCount) + 1; i < count - 1; i++) hide(i) + show(0) + show(count - 1) + leftEllipsis.style.display = "block" + rightEllipsis.style.display = "block" + } + } + } + } + let refresh = () => { + if ((pageSizeInput == null) || (parseInt(pageSizeInput.value) <= 0)) { + for (let pagination of paginations) pagination.style.display = "none" + return + } + let pageSize = parseInt(pageSizeInput.value) + let pageCount = Math.ceil(contents.length / pageSize) + if (!flags.isFirst && (flags.pageSize == pageSize) && (flags.pageCount == pageCount)) return + for (let pagination of paginations) { + let buttons = pagination.querySelector("ul.pagination-list") + buttons.innerHTML = "" + buttons.id = "tabs" + } + for (let i = 0; i < pageCount; i++) { + for (let j = i * pageSize; j < (i + 1) * pageSize; j++) { + if (contents[j] == null) break + contents[j].classList.add("tab-content") + contents[j].setAttribute("tab-group", group) + contents[j].setAttribute("tab-index", i) + if ((i == 0) && (flags.isFirst || (flags.pageCount != pageCount))) contents[j].classList.add("is-active") + if (j == ((i + 1) * pageSize - 1)) for (let td of contents[j].querySelectorAll("td")) td.style.borderBottom = "0" + } + if (pageCount > 1) for (let pagination of paginations) { + let buttons = pagination.querySelector("ul.pagination-list") + let a = document.createElement("a") + a.classList.add("pagination-link") + a.innerText = i + 1 + let li = document.createElement("li") + li.appendChild(a) + if ((i == 0) && (flags.isFirst || (flags.pageCount != pageCount))) { + li.classList.add("is-active") + a.classList.add("is-current") + } + li.setAttribute("tab-group", group) + li.setAttribute("tab-index", i) + buttons.appendChild(li) + li.addEventListener("click", () => { + refreshEllipsis(li) + }) + } + } + if (pageCount > 1) for (let pagination of paginations) { + pagination.style.display = "flex" + let buttons = pagination.querySelector("ul.pagination-list") + let leftEllipsis = document.createElement("li") + leftEllipsis.style.display = "none" + leftEllipsis.classList.add("ellipsis-left", "ignore") + leftEllipsis.innerHTML = "" + let rightEllipsis = document.createElement("li") + rightEllipsis.style.display = "none" + rightEllipsis.classList.add("ellipsis-right", "ignore") + rightEllipsis.innerHTML = "" + buttons.firstChild.after(leftEllipsis) + buttons.lastChild.before(rightEllipsis) + + let previousButton = pagination.querySelector("a.pagination-previous") + let nextButton = pagination.querySelector("a.pagination-next") + previousButton.addEventListener("click", () => { + let current = buttons.querySelector("li.is-active") + let index = parseInt(current.getAttribute("tab-index")) + if (index <= 0) return + let prev = buttons.querySelector("li[tab-index=\"" + (index - 1) + "\"]") + prev.dispatchEvent(new Event("click")) + }) + nextButton.addEventListener("click", () => { + let current = buttons.querySelector("li.is-active") + let index = parseInt(current.getAttribute("tab-index")) + if (index >= (buttons.childElementCount - 3)) return // includes left & right ellipsis + let next = buttons.querySelector("li[tab-index=\"" + (index + 1) + "\"]") + next.dispatchEvent(new Event("click")) + }) + } else for (let pagination of paginations) pagination.style.display = "none" + flags.pageCount = pageCount + flags.pageSize = pageSize + flags.isFirst = false + } + refresh() + pageSizeInput.addEventListener("change", refresh) + let o = new ResizeObserver(refreshEllipsis) + o.observe(container) + } +} + +function initializeTabs() { + let tabs = document.querySelectorAll("#tabs li") + let tabContents = document.querySelectorAll("#tab-content, .tab-content") + let updateActiveTab = (tabGroup, tabIndex) => { + for (let t of tabs) if (t && (t.getAttribute("tab-group") == tabGroup)) { + if (t.getAttribute("tab-index") != tabIndex) { + t.classList.remove("is-active") + for (let a of t.querySelectorAll("a")) a.classList.remove("is-current") + } else { + t.classList.add("is-active") + for (let a of t.querySelectorAll("a")) a.classList.add("is-current") + } + } + } + + let updateActiveContent = (tabGroup, tabIndex) => { + for (let item of tabContents) { + let group = item.getAttribute("tab-group") + let index = item.getAttribute("tab-index") + if (item && (group == tabGroup)) item.classList.remove("is-active") + if ((index == tabIndex) && (group == tabGroup)) item.classList.add("is-active") + } + } + for (let t of tabs) { + if (!t.classList.contains("disabled") && !t.classList.contains("ignore")) t.addEventListener("click", () => { + let group = t.getAttribute("tab-group") + let index = t.getAttribute("tab-index") + updateActiveTab(group, index) + updateActiveContent(group, index) + }) + } +} + +function initializeToggles() { + let toggles = document.querySelectorAll(".card-header .card-toggle") + let contents = document.querySelectorAll(".card-content") + + for (let t of toggles) { + let card = t.getAttribute("card") + if (card == null) continue + let cc = [] + for (let c of contents) if (c.getAttribute("card") == card) cc.push(c) + t.style.transition = "0.2s linear" + t.addEventListener("click", (e) => { + if (e.currentTarget.style.transform == "rotate(180deg)") { + e.currentTarget.style.transform = "" + for (let c of cc) c.classList.remove("is-hidden") + } else { + e.currentTarget.style.transform = "rotate(180deg)" + for (let c of cc) c.classList.add("is-hidden") + } + }) + } +} + +function initializeModals() { + let modaltriggers = $(".modal-trigger") + for (let t of modaltriggers) { + let m = t.querySelector(".modal") + let c = m.querySelectorAll("#close") + t.addEventListener("click", (e) => { m.style.display = "flex" }) + for (let v of c) v.addEventListener("click", (e) => { + m.style.display = "none" + e.stopPropagation() + }) + } +} + +function initializeFormSelects() { + let formSelects = document.querySelectorAll("#form-select") + for (let s of formSelects) { + let input = s.querySelector("input#form-select-input") + let select = s.querySelector("select#form-select-select") + let options = select.querySelectorAll("option") + for (let i = 0; i < options.length; i++) { + let o = options[i] + let value = (o.getAttribute("value") == null) ? i : o.getAttribute("value") + let enabled = (o.getAttribute("disabled") == null) ? true : false + if (value == input.value) select.selectedIndex = i + if (!enabled) o.style.display = "none" + } + select.addEventListener("change", () => { + for (let i = 0; i < options.length; i++) { + let o = options[i] + if (o.selected) { + input.value = (o.getAttribute("value") == null) ? i : o.getAttribute("value") + input.dispatchEvent(new Event("change")) + break + } + } + }) + } +} + +function initializeFormPaginations() { + let formPags = document.querySelectorAll("#form-pagination") + for (let p of formPags) { + let input = p.querySelector("input#form-pagination-input") + let options = p.querySelectorAll("ul.pagination-list li a.pagination-link") + for (let i = 0; i < options.length; i++) { + let o = options[i] + let value = (o.getAttribute("value") == null) ? i : o.getAttribute("value") + if (value == input.value) { + if (!o.classList.contains("is-current")) o.classList.add("is-current") + } else o.classList.remove("is-current") + o.addEventListener("click", () => { + for (let i = 0; i < options.length; i++) options[i].classList.remove("is-current") + if (!o.classList.contains("is-current")) o.classList.add("is-current") + input.value = (o.getAttribute("value") == null) ? i : o.getAttribute("value") + }) + } + } +} + +function initializeFormValidation() { + let forms = document.querySelectorAll("form#validatable") + for (let f of forms) { + let validatableFields = f.querySelectorAll(".field#validatable") + let validatableButtons = f.querySelectorAll("button#validatable") + + let getParams = (input) => { + return { + minLength: input.getAttribute("min-length"), + maxLength: input.getAttribute("max-length"), + recommendedLength: input.getAttribute("recommended-length"), + minPattern: input.getAttribute("min-pattern"), + recommendedPattern: input.getAttribute("recommended-pattern"), + isNumeric: (input.getAttribute("numeric") != null) ? true : false + } + } + let isValid = (value, params) => { + let t = value.trim() + if (params.minLength != null) if (t.length < parseInt(params.minLength)) return false + if (params.maxLength != null) if (t.length > parseInt(params.maxLength)) return false + if (params.minPattern != null) if (!(new RegExp(params.minPattern).test(t))) return false + if (params.isNumeric == true) if (parseInt(t).toString() != t) return false + return true + } + + let isFormValid = () => { + for (let field of validatableFields) for (let i of field.querySelectorAll("input#validatable")) if (!isValid(i.value, getParams(i))) return false + return true + } + + for (let field of validatableFields) { + let inputs = field.querySelectorAll("input#validatable") + let tips = field.querySelectorAll(".help") + for (let i of inputs) i.addEventListener("change", () => { + let params = getParams(i) + // inputs + if (isValid(i.value, params)) { + i.classList.remove("is-danger") + for (let t of tips) t.classList.remove("is-danger") + } else if (!i.classList.contains("is-danger")) { + i.classList.add("is-danger") + for (let t of tips) t.classList.add("is-danger") + } + // buttons + if (isFormValid()) { + for (let b of validatableButtons) b.removeAttribute("disabled") + } else { + for (let b of validatableButtons) if (b.getAttribute("disabled") == null) b.setAttribute("disabled", "") + } + }) + } + } +} + +function initializeFormCollections() { + let collections = document.querySelectorAll("#form-collection") + for (let c of collections) { + let maxLength = parseInt(c.getAttribute("max-length")) + let fallbackValue = JSON.parse(c.getAttribute("fallback")) + let input = c.querySelector("#form-collection-input") + let tags = c.querySelectorAll("#form-collection-tag") + let modButton = c.querySelector("#form-collection-modify") + let modTable = c.querySelector("table#multi-select") + let modInput = modTable.querySelector("input#multi-select-input") + let modTitle = modTable.querySelector("input#multi-select-title") + let deleteButtonClickEventListener = (tag) => () => { + let tvalue = JSON.parse(tag.getAttribute("value")) + let value = JSON.parse(input.value) + value.splice(value.indexOf(tvalue), 1) + if (fallbackValue != null) value.push(fallbackValue) + input.value = JSON.stringify(value) + modInput.value = input.value + modInput.dispatchEvent(new Event("change")) + tag.remove() + } + + for (let t of tags) { + let d = t.querySelector(".delete, .is-delete") + d.addEventListener("click", deleteButtonClickEventListener(t)) + } + modInput.value = input.value + modInput.setAttribute("max-length", maxLength) + modInput.setAttribute("fallback", JSON.stringify(fallbackValue)) + modInput.addEventListener("change", () => { + let fallbackValue = JSON.parse(c.getAttribute("fallback")) + let oldValue = JSON.parse(input.value) + let newValue = JSON.parse(modInput.value) + let tags = c.querySelectorAll("#form-collection-tag") + for (let o of oldValue) if (!newValue.includes(o) && (o != fallbackValue)) { + for (let t of tags) if (JSON.parse(t.getAttribute("value")) == o) t.remove() + } + for (let n = 0; n < newValue.length; n++) if (!oldValue.includes(newValue[n]) && (newValue[n] != fallbackValue)) { + let tag = document.createElement("div") + tag.classList.add("control") + tag.id = "form-collection-tag" + tag.setAttribute("value", newValue[n]) + tag.innerHTML = "" + JSON.parse(modTitle.value)[n] + "" + tag.querySelector("a.is-delete").addEventListener("click", deleteButtonClickEventListener(tag)) + modButton.before(tag) + } + input.value = modInput.value + }) + } +} + +function initializeMultiSelectTables() { + let tables = document.querySelectorAll("table#multi-select") + for (let table of tables) { + let valueInput = table.querySelector("input#multi-select-input") + let titleInput = table.querySelector("input#multi-select-title") + let trimValues = (values, fallback) => { + while (values.includes(fallback)) values.splice(values.indexOf(fallback), 1) + return values + } + let fillValues = (values, fallback) => { + let maxLength = (valueInput.getAttribute("max-length") == null) ? -1 : parseInt(valueInput.getAttribute("max-length")) + while (values.length < maxLength) values.push(fallback) + return values + } + let lines = table.querySelectorAll("tbody tr") + let refresh = () => { + let fallbackValue = JSON.parse(valueInput.getAttribute("fallback")) + let value = trimValues(JSON.parse(valueInput.value), fallbackValue) + let title = [] + for (let l of lines) { + let lvalue = JSON.parse(l.getAttribute("multi-select-value")) + if (value.includes(lvalue)) { + if (!l.classList.contains("is-selected")) l.classList.add("is-selected") + title[value.indexOf(lvalue)] = l.getAttribute("multi-select-title") + l.style.fontWeight = "bold" + } else { + l.classList.remove("is-selected") + l.style.fontWeight = "" + } + } + titleInput.value = JSON.stringify(title) + } + + for (let l of lines) { + l.onclick = () => { + let fallbackValue = JSON.parse(valueInput.getAttribute("fallback")) + let maxLength = (valueInput.getAttribute("max-length") == null) ? -1 : parseInt(valueInput.getAttribute("max-length")) + let value = trimValues(JSON.parse(valueInput.value), fallbackValue) + let lvalue = JSON.parse(l.getAttribute("multi-select-value")) + if (value.includes(lvalue)) value.splice(value.indexOf(lvalue), 1) + else if (maxLength >= 0) { + if (value.length < maxLength) value.push(lvalue) + else alert("Cannot add more items, items are up to " + maxLength + ".") + } else value.push(lvalue) + valueInput.value = JSON.stringify(fillValues(value, fallbackValue)) + refresh() + valueInput.dispatchEvent(new Event("change")) + } + refresh() + } + valueInput.addEventListener("change", refresh) + } +} + +function initializeFormNumerics() { + let numerics = document.querySelectorAll("#form-numeric") + for (let n of numerics) { + let add = n.querySelector("#form-numeric-add") + let sub = n.querySelector("#form-numeric-sub") + let inputs = n.querySelectorAll("#form-numeric-input") + add.addEventListener("click", (e) => { + for (let i of inputs) { + let maxValue = parseFloat(i.getAttribute("max-value")) + let step = parseFloat(i.getAttribute("step")) + + let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count")) + let value = (parseFloat(i.value) * 10 + step * 10) / 10 + if (value * Math.sign(step) <= maxValue * Math.sign(step)) i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value + } + e.stopPropagation() + }) + sub.addEventListener("click", (e) => { + for (let i of inputs) { + let minValue = parseFloat(i.getAttribute("min-value")) + let step = parseFloat(i.getAttribute("step")) + let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count")) + let value = (parseFloat(i.value) * 10 - step * 10) / 10 + if (value * Math.sign(step) >= minValue * Math.sign(step)) i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value + } + e.stopPropagation() + }) + for (let i of inputs) { + let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count")) + let value = parseFloat(i.value) + i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value + } + } +} + +function initializeUploader() { + let uploaders = document.querySelectorAll("div#uploader") + for (let uploader of uploaders) { + let input = uploader.querySelector("input#uploader-input") + let text = uploader.querySelector("input#uploader-text") + let placeholder = uploader.querySelector("#uploader-placeholder") + let remove = uploader.querySelector("#uploader-delete") + let reader = new FileReader() + input.addEventListener("change", () => { + if (input.files.length > 0) { + remove.style.display = "block" + placeholder.innerText = input.files[0].name + reader.readAsText(input.files[0]) + reader.onload = () => text.value = reader.result + } else { + placeholder.innerText = "" + remove.style.display = "none" + text.value = null + } + }) + remove.addEventListener("click", (e) => { + e.stopPropagation() + input.value = null + input.dispatchEvent(new Event("change")) + }) + + remove.style.display = "none" + } +} + +function checkImg() { + let imgs = document.querySelectorAll("#exist-or-not") + for (let img of imgs) { + let general = img.querySelector("img#general") + let specified = img.querySelector("img#specified") + + if (specified.width == 0) specified.style.display = "none" + else general.style.display = "none" + } +} + +function initializeMarqueeLabels() { + let marqueeContainers = document.querySelectorAll(".marquee-label-container") + for (let c of marqueeContainers) { + let marquees = c.querySelectorAll(".marquee-label") + for (let marquee of marquees) { + if (marquee.closest(".marquee-label-container") != c) continue + let refresh = () => { + let lpad = parseInt(window.getComputedStyle(c, null).getPropertyValue("padding-left")) + if (lpad == NaN) lpad = 0 + let rpad = parseInt(window.getComputedStyle(c, null).getPropertyValue("padding-right")) + if (rpad == NaN) rpad = 20 + let hpad = lpad + rpad + let speed = marquee.getAttribute("speed") + if (speed == null) speed = 1 + let stopingTime = 0.5 + let duration = (20 * (marquee.offsetWidth - c.offsetWidth + hpad)) / speed + 2 * stopingTime + if ((marquee.offsetWidth > 0) && (marquee.offsetWidth > c.offsetWidth - hpad)) { + marquee.animate([ + { transform: "translateX(0)", offset: 0 }, + { transform: "translateX(0)", easing: "cubic-bezier(0.67, 0, 0.33, 1)", offset: stopingTime / duration }, + { transform: "translateX(" + (c.offsetWidth - marquee.offsetWidth - hpad) + "px)", easing: "cubic-bezier(0.67, 0, 0.33, 1)", offset: 1 - stopingTime / duration }, + { transform: "translateX(" + (c.offsetWidth - marquee.offsetWidth - hpad) + "px)", offset: 1 } + ], { duration: (20 * (marquee.offsetWidth - c.offsetWidth) + 1000) / speed, direction: "alternate-reverse", iterations: Infinity }) + } else marquee.style.animation = "none" + } + let o = new ResizeObserver(refresh) + o.observe(c) + } + } +} + +function initializeNotificatioAnimation() { + let notifications = document.querySelectorAll(".notification.temporary") + for (let n of notifications) { + let remove = n.querySelector(".delete") + let startSubmitter = n.querySelector("form.start") + let startPath = startSubmitter.getAttribute("action") + let startRequest = new XMLHttpRequest() + startRequest.open("POST", startPath, true) + startRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded") + + let endSubmitter = n.querySelector("form.end") + let endPath = startSubmitter.getAttribute("action") + let endRequest = new XMLHttpRequest() + endRequest.open("POST", endPath, true) + endRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded") + + if (startSubmitter != null) startRequest.send() + let end = () => { + n.style.display = "none" + if (endSubmitter != null) endRequest.send() + } + + n.style.animationPlayState = "running" + remove.addEventListener("click", end) + n.addEventListener("animationend", end) + n.addEventListener("webkitAnimationEnd", end) + } +} + +function initializeCheckBoxes() { + let checks = document.querySelectorAll(".checkbox") + for (let c of checks) { + let input = c.querySelector("input[type=checkbox]") + let mark = c.querySelector(".checkmark") + let refresh = (value) => { + value = input.getAttribute("checked") + if (value == null) { + input.removeAttribute("checked") + mark.style.opacity = 0 + if (!c.classList.contains("is-light")) c.classList.add("is-light") + } else { + input.setAttribute("checked", "checked") + mark.style.opacity = 100 + c.classList.remove("is-light") + } + } + c.addEventListener("click", () => { + let value = input.getAttribute("checked") + if (value == null) input.setAttribute("checked", "checked") + else input.removeAttribute("checked") + refresh() + }) + refresh() + } +} + +function removeLoadingModal() { + let loading = document.querySelector(".loading") + setTimeout(() => (loading == null) ? null : loading.remove(), 505) + try { + let a = loading.animate([ + { offset: 0, opacity: 1 }, + { offset: 0.25, opacity: 0 }, + { offset: 1, opacity: 0 } + ], { duration: 2000 }) + a.onfinish = loading.remove + a.play() + } catch { } +} + +$(document).ready(() => { + initializeNotificatioAnimation() + initializePaginatedContent() + initializeTabs() + initializeToggles() + initializeModals() + initializeFormSelects() + initializeFormNumerics() + initializeFormPaginations() + initializeFormValidation() + initializeFormCollections() + initializeMultiSelectTables() + initializeUploader() + checkImg() + initializeMarqueeLabels() + initializeCheckBoxes() + + removeLoadingModal() +}) + diff --git a/bst@asphyxia/webui/profile_detail.pug b/bst@asphyxia/webui/profile_detail.pug new file mode 100644 index 0000000..44b8094 --- /dev/null +++ b/bst@asphyxia/webui/profile_detail.pug @@ -0,0 +1,750 @@ +//DATA// + bst2Account: DB.FindOne(refid, { collection: "bst.bst2.player.account" }) + bst2Base: DB.FindOne(refid, { collection: "bst.bst2.player.base" }) + bst2Customization: DB.FindOne(refid, { collection: "bst.bst2.player.customization" }) + bst2Course: DB.Find({ collection: "bst.bst2.playData.course#userId" }) + bst2MusicRecord: DB.Find({ collection: "bst.bst2.playData.musicRecord#userId" }) + bst2CourseLog: DB.Find({ collection: "bst.bst2.playData.courseLog#userId" }) + bst2StageLog: DB.Find({ collection: "bst.bst2.playData.stageLog#userId" }) + + webuiMessage: DB.FindOne({ collection: "utility.webuiMessage" }) + +//- Definitions +- + let defaultVersion = 2 + + let bstMusicsInfo = [{ musicId: 0, title: "SAVIOR OF SONG", version: 1, category: "ANIME", bpm: { min: 192, max: 192 }, chartsInfo: { light: "02", medium: "08", beast: "09", nightmare: "10" }, artist: "ナノ feat. MY FIRST STORY" }, { musicId: 1, title: "マネマネサイコトロピック", version: 1, category: "EXITTUNES", bpm: { min: 135, max: 210 }, chartsInfo: { light: "02", medium: "06", beast: "09⁺" }, artist: "かいりきベア" }, { musicId: 2, title: "ミスターデジャブ", version: 1, category: "EXITTUNES", bpm: { min: 230, max: 230 }, chartsInfo: { light: "01", medium: "06", beast: "10⁻" }, artist: "164 feat.MAYU" }, { musicId: 3, title: "セツナトリップ", version: 1, category: "EXITTUNES", bpm: { min: 145, max: 145 }, chartsInfo: { light: "01", medium: "06", beast: "09⁻", nightmare: "10" }, artist: "Last Note. feat. GUMI" }, { musicId: 4, title: "ラクガキスト", version: 1, category: "EXITTUNES", bpm: { min: 199, max: 199 }, chartsInfo: { light: "05", medium: "09⁻", beast: "09⁺", nightmare: "10⁺" }, artist: "cosMo@暴走P feat.GUMI" }, { musicId: 5, title: "放課後ストライド", version: 1, category: "EXITTUNES", bpm: { min: 225, max: 225 }, chartsInfo: { light: "04", medium: "07", beast: "09⁻", nightmare: "10" }, artist: "Last Note." }, { musicId: 6, title: "リズの内心革命", version: 1, category: "EXITTUNES", bpm: { min: 180, max: 180 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "じっぷす" }, { musicId: 7, title: "ロストワンの号哭", version: 1, category: "EXITTUNES", bpm: { min: 162, max: 162 }, chartsInfo: { light: "03", medium: "07", beast: "10", nightmare: "10" }, artist: "Neru" }, { musicId: 8, title: "HΨ=世界創造=EΨ", version: 1, category: "EXITTUNES", bpm: { min: 208, max: 208 }, chartsInfo: { light: "03", medium: "08", beast: "10" }, artist: "黒猫アンティーク feat.IA,GUMI" }, { musicId: 9, title: "Nyan Cat", version: 1, category: "EXITTUNES", bpm: { min: 142, max: 142 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "daniwellP feat. 桃音モモ" }, { musicId: 10, title: "天ノ弱", version: 1, category: "EXITTUNES", bpm: { min: 200, max: 205 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "164" }, { musicId: 11, title: "恋愛勇者", version: 1, category: "EXITTUNES", bpm: { min: 190, max: 190 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "Last Note." }, { musicId: 12, title: "ねこみみスイッチ", version: 1, category: "EXITTUNES", bpm: { min: 160, max: 160 }, chartsInfo: { light: "02", medium: "06", beast: "08", nightmare: "09⁻" }, artist: "daniwellP" }, { musicId: 13, title: "にゃにゃにゃんにゃんにゃーのこねこ", version: 1, category: "EXITTUNES", bpm: { min: 184, max: 184 }, chartsInfo: { light: "02", medium: "06", beast: "09⁺" }, artist: "daniwellP" }, { musicId: 14, title: "過食性:アイドル症候群", version: 1, category: "EXITTUNES", bpm: { min: 200, max: 200 }, chartsInfo: { light: "04", medium: "08", beast: "10", nightmare: "10" }, artist: "スズム" }, { musicId: 15, title: "チルノのパーフェクトさんすう教室", version: 1, category: "TOHO", bpm: { min: 175, max: 175 }, chartsInfo: { light: "02", medium: "07", beast: "09⁻", nightmare: "09⁺" }, artist: "ARM+夕野ヨシミ feat. miko" }, { musicId: 16, title: "待チ人ハ来ズ。", version: 1, category: "TOHO", bpm: { min: 160, max: 160 }, chartsInfo: { light: "03", medium: "06", beast: "08" }, artist: "豚乙女" }, { musicId: 17, title: "月に叢雲華に風", version: 1, category: "TOHO", bpm: { min: 160, max: 160 }, chartsInfo: { light: "04", medium: "07", beast: "09⁻", nightmare: "10-" }, artist: "幽閉サテライト(Arranged:Iceon) feat. senya" }, { musicId: 18, title: "物凄い勢いでけーねが物凄いうた", version: 1, category: "TOHO", bpm: { min: 180, max: 180 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "Halozy feat. ななひら" }, { musicId: 19, title: "INFINITE WORLD", version: 1, category: "TOHO", bpm: { min: 167, max: 167 }, chartsInfo: { light: "03", medium: "07", beast: "09", nightmare: "10" }, artist: "SOUND HOLIC feat. Nana Takahashi" }, { musicId: 20, title: "ウサテイ", version: 1, category: "TOHO", bpm: { min: 190, max: 190 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "あまね+ビートまりお(COOL&CREATE)" }, { musicId: 21, title: "魔理沙は大変なものを盗んでいきました", version: 1, category: "TOHO", bpm: { min: 170, max: 170 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺", nightmare: "10" }, artist: "ARM+夕野ヨシミ feat. 藤咲かりん" }, { musicId: 22, title: "色は匂へど 散りぬるを", version: 1, category: "TOHO", bpm: { min: 138, max: 138 }, chartsInfo: { light: "03", medium: "06", beast: "08" }, artist: "幽閉サテライト(Arranged:Iceon) feat. senya" }, { musicId: 23, title: "お嫁にしなさいっ!", version: 1, category: "TOHO", bpm: { min: 180, max: 180 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "ARM+夕野ヨシミ" }, { musicId: 24, title: "しゅわスパ大作戦☆", version: 1, category: "TOHO", bpm: { min: 140, max: 140 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "SOUND HOLIC feat. Nana Takahashi" }, { musicId: 25, title: "げきオコスティックファイナリアリティぷんぷんマスタースパーク", version: 1, category: "TOHO", bpm: { min: 200, max: 200 }, chartsInfo: { light: "04", medium: "08", beast: "09⁺" }, artist: "ARM+ビートまりお(COOL&CREATE)" }, { musicId: 26, title: "ケロ⑨destiny", version: 1, category: "TOHO", bpm: { min: 172, max: 172 }, chartsInfo: { light: "02", medium: "07", beast: "09⁺" }, artist: "Silver Forest feat. めらみぽっぷ" }, { musicId: 27, title: "Miracle Halloween", version: 1, category: "KDE", bpm: { min: 140, max: 140 }, chartsInfo: { light: "02", medium: "06", beast: "08" }, artist: "Sana" }, { musicId: 28, title: "ミライノトビラ", version: 1, category: "KDE", bpm: { min: 163, max: 163 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "Sana" }, { musicId: 29, title: "ちくわパフェだよ☆CKP", version: 1, category: "KDE", bpm: { min: 205, max: 205 }, chartsInfo: { light: "04", medium: "08", beast: "09⁺", nightmare: "10" }, artist: "日向美ビタースイーツ♪" }, { musicId: 30, title: "Colorful Days ~NEWラブプラス メインテーマ~", version: 1, category: "KDE", bpm: { min: 128, max: 128 }, chartsInfo: { light: "02", medium: "06", beast: "08" }, artist: "高嶺愛花&小早川凛子&姉ヶ崎寧々" }, { musicId: 31, title: "Rainbow Magic", version: 1, category: "KDE", bpm: { min: 124, max: 150 }, chartsInfo: { light: "02", medium: "06", beast: "08" }, artist: "マジックアカデミー管弦楽部×劇団レコード" }, { musicId: 32, title: "チュートリアル", version: 2, category: "KDE", bpm: { min: 100, max: 100 }, chartsInfo: { light: "01" }, artist: "-" }, { musicId: 33, title: "回レ!雪月花", version: 1, category: "ANIME", bpm: { min: 160, max: 160 }, chartsInfo: { light: "04", medium: "08", beast: "10", nightmare: "10" }, artist: "歌組雪月花" }, { musicId: 34, title: "Anicca", version: 1, category: "ANIME", bpm: { min: 175, max: 175 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "原田ひとみ" }, { musicId: 35, title: "This game", version: 1, category: "ANIME", bpm: { min: 147, max: 147 }, chartsInfo: { light: "02", medium: "04", beast: "09⁻", nightmare: "09⁺" }, artist: "鈴木このみ" }, { musicId: 36, title: "オラシオン", version: 1, category: "ANIME", bpm: { min: 166, max: 166 }, chartsInfo: { light: "01", medium: "04", beast: "08", nightmare: "09⁻" }, artist: "白(CV:茅野愛衣)" }, { musicId: 37, title: "共鳴のTrue Force", version: 1, category: "ANIME", bpm: { min: 187.5, max: 187.5 }, chartsInfo: { light: "04", medium: "07", beast: "09⁺" }, artist: "原田ひとみ" }, { musicId: 38, title: "精霊剣舞祭", version: 1, category: "ANIME", bpm: { min: 187, max: 187 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "にーそっくすす" }, { musicId: 39, title: "祝祭のエレメンタリア", version: 1, category: "ANIME", bpm: { min: 150, max: 150 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "にーそっくすす" }, { musicId: 40, title: "幻想系世界修復少女", version: 1, category: "EXITTUNES", bpm: { min: 193, max: 193 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "Last Note." }, { musicId: 41, title: "バンブーソード・ガール", version: 1, category: "EXITTUNES", bpm: { min: 208, max: 208 }, chartsInfo: { light: "04", medium: "08", beast: "09" }, artist: "cosMo@暴走P" }, { musicId: 42, title: "Idola", version: 1, category: "KDE", bpm: { min: 201, max: 201 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "iconoclasm feat.GUMI" }, { musicId: 43, title: "サリシノハラ", version: 1, category: "EXITTUNES", bpm: { min: 135, max: 135 }, chartsInfo: { light: "02", medium: "06", beast: "08", nightmare: "09⁻" }, artist: "みきとP" }, { musicId: 44, title: "脱出ゲヱム", version: 1, category: "EXITTUNES", bpm: { min: 134, max: 134 }, chartsInfo: { light: "04", medium: "07", beast: "10⁻" }, artist: "ゆちゃP" }, { musicId: 45, title: "ケッペキショウ", version: 1, category: "EXITTUNES", bpm: { min: 190, max: 190 }, chartsInfo: { light: "04", medium: "08", beast: "10⁻" }, artist: "すこっぷ" }, { musicId: 46, title: "僕は空気が嫁ない", version: 1, category: "EXITTUNES", bpm: { min: 192, max: 192 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "cosMo@暴走P" }, { musicId: 47, title: "sweet little sister", version: 1, category: "TOHO", bpm: { min: 168, max: 168 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "Silver Forest feat. さゆり" }, { musicId: 48, title: "泡沫、哀のまほろば", version: 1, category: "TOHO", bpm: { min: 155, max: 155 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "幽閉サテライト(Arranged:Iceon,HiZuMi) feat. senya" }, { musicId: 49, title: "最終鬼畜妹フランドール・S", version: 1, category: "TOHO", bpm: { min: 200, max: 200 }, chartsInfo: { light: "04", medium: "07", beast: "10⁻", nightmare: "10" }, artist: "ビートまりお(COOL&CREATE)" }, { musicId: 50, title: "Help me, ERINNNNNN!!", version: 1, category: "TOHO", bpm: { min: 183, max: 183 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻", nightmare: "10-" }, artist: "ビートまりお(COOL&CREATE)" }, { musicId: 51, title: "ナイト・オブ・ナイツ", version: 1, category: "TOHO", bpm: { min: 180, max: 180 }, chartsInfo: { light: "04", medium: "08", beast: "10", nightmare: "10" }, artist: "ビートまりお(COOL&CREATE)" }, { musicId: 52, title: "†渚の小悪魔ラヴリィ~レイディオ† ", version: 1, category: "KDE", bpm: { min: 190, max: 190 }, chartsInfo: { light: "03", medium: "08", beast: "09⁺" }, artist: "夏色ビキニのPrim" }, { musicId: 53, title: "恋する☆宇宙戦争っ!!", version: 1, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "04", medium: "08", beast: "10", nightmare: "ネ申" }, artist: "Prim" }, { musicId: 54, title: "狂イ咲ケ焔ノ華", version: 1, category: "KDE", bpm: { min: 206, max: 206 }, chartsInfo: { light: "04", medium: "08", beast: "10⁻" }, artist: "覚醒ノPrim" }, { musicId: 55, title: "めうめうぺったんたん!!", version: 1, category: "KDE", bpm: { min: 185, max: 185 }, chartsInfo: { light: "04", medium: "08", beast: "10", nightmare: "10" }, artist: "日向美ビタースイーツ♪" }, { musicId: 56, title: "カタルシスの月", version: 1, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "日向美ビタースイーツ♪" }, { musicId: 57, title: "凛として咲く花の如く ~ひなビタ♪edition~", version: 1, category: "KDE", bpm: { min: 163, max: 163 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻", nightmare: "09⁺" }, artist: "日向美ビタースイーツ♪" }, { musicId: 58, title: "滅亡天使 † にこきゅっぴん", version: 1, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "日向美ビタースイーツ♪" }, { musicId: 59, title: "Bad Apple!! feat. nomico", version: 1, category: "TOHO", bpm: { min: 138, max: 138 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺", nightmare: "10-" }, artist: "Alstroemeria Records" }, { musicId: 60, title: "ビビットストリーム", version: 1, category: "KDE", bpm: { min: 184, max: 184 }, chartsInfo: { light: "03", medium: "08", beast: "09⁻", nightmare: "10-" }, artist: "DJ TOTTO" }, { musicId: 61, title: "groovin'", version: 1, category: "KDE", bpm: { min: 140, max: 140 }, chartsInfo: { light: "04", medium: "08", beast: "09⁺" }, artist: "Sota Fujimori" }, { musicId: 62, title: "センチメンタルラブ", version: 1, category: "ANIME", bpm: { min: 138, max: 138 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "みみめめMIMI" }, { musicId: 63, title: "sister’s noise", version: 1, category: "ANIME", bpm: { min: 144, max: 144 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻", nightmare: "09⁺" }, artist: "fripSide" }, { musicId: 64, title: "Daydream café", version: 1, category: "ANIME", bpm: { min: 150, max: 150 }, chartsInfo: { light: "05", medium: "08", beast: "09⁺", nightmare: "10" }, artist: "Petit Rabbit’s" }, { musicId: 65, title: "閃光の行方", version: 1, category: "ANIME", bpm: { min: 175, max: 175 }, chartsInfo: { light: "03", medium: "10", beast: "10" }, artist: "Falcom Sound Team jdk" }, { musicId: 66, title: "I'll remember you", version: 1, category: "ANIME", bpm: { min: 148, max: 148 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "Falcom Sound Team jdk" }, { musicId: 67, title: "乙女繚乱 舞い咲き誇れ", version: 1, category: "KDE", bpm: { min: 210, max: 210 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "日向美ビタースイーツ♪" }, { musicId: 68, title: "Harmonia", version: 1, category: "KDE", bpm: { min: 177, max: 177 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "ATSUMI UEDA" }, { musicId: 69, title: "青春セッション PARADISE", version: 1, category: "OTHER", bpm: { min: 150, max: 190 }, chartsInfo: { light: "03", medium: "07", beast: "10", nightmare: "10" }, artist: "A応P" }, { musicId: 70, title: "きょうもハレバレ", version: 1, category: "EXITTUNES", bpm: { min: 134, max: 134 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "ふわりP feat. GUMI's" }, { musicId: 71, title: "ノイジーラバーソウル", version: 1, category: "EXITTUNES", bpm: { min: 190, max: 190 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10" }, artist: "Last Note." }, { musicId: 72, title: "0", version: 1, category: "EXITTUNES", bpm: { min: 168, max: 168 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "cosMo@暴走P" }, { musicId: 73, title: "いーあるふぁんくらぶ", version: 1, category: "EXITTUNES", bpm: { min: 145, max: 145 }, chartsInfo: { light: "02", medium: "04", beast: "09⁻" }, artist: "みきとP" }, { musicId: 74, title: "幸せになれる隠しコマンドがあるらしい", version: 1, category: "EXITTUNES", bpm: { min: 142, max: 142 }, chartsInfo: { light: "01", medium: "06", beast: "09⁺", nightmare: "09⁺" }, artist: "うたたP feat. 結月ゆかり" }, { musicId: 75, title: "Phantasm Brigade", version: 1, category: "TOHO", bpm: { min: 168, max: 168 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "Silver Forest" }, { musicId: 76, title: "突撃!ガラスのニーソ姫!", version: 1, category: "KDE", bpm: { min: 185, max: 185 }, chartsInfo: { light: "03", medium: "08", beast: "10⁻" }, artist: "山本椛 (monotone)" }, { musicId: 77, title: "キャトられ♥恋はモ~モク", version: 1, category: "KDE", bpm: { min: 196, max: 196 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "ギュ~っとしたい♥Prim" }, { musicId: 78, title: "即席!脳直★ミュージックシステム", version: 1, category: "KDE", bpm: { min: 197, max: 197 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "MOSAIC.WAV" }, { musicId: 79, title: "Synchrogazer", version: 1, category: "ANIME", bpm: { min: 159, max: 159 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻", nightmare: "10" }, artist: "水樹奈々" }, { musicId: 80, title: "家出少年と迷子少女", version: 1, category: "EXITTUNES", bpm: { min: 165, max: 165 }, chartsInfo: { light: "02", medium: "07", beast: "09⁻" }, artist: "cosMo@暴走P feat. GUMI" }, { musicId: 81, title: "白い雪のプリンセスは", version: 1, category: "EXITTUNES", bpm: { min: 200, max: 200 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻", nightmare: "09⁺" }, artist: "のぼる↑" }, { musicId: 82, title: "命のユースティティア", version: 1, category: "EXITTUNES", bpm: { min: 156, max: 156 }, chartsInfo: { light: "04", medium: "08", beast: "09⁺" }, artist: "Neru" }, { musicId: 83, title: "再教育", version: 1, category: "EXITTUNES", bpm: { min: 163, max: 163 }, chartsInfo: { light: "02", medium: "07", beast: "09⁻" }, artist: "Neru" }, { musicId: 84, title: "ドロボウナイトトリック", version: 1, category: "EXITTUNES", bpm: { min: 218, max: 218 }, chartsInfo: { light: "05", medium: "09⁻", beast: "09⁺" }, artist: "ゆちゃP" }, { musicId: 85, title: "茅蜩モラトリアム", version: 1, category: "EXITTUNES", bpm: { min: 189, max: 189 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10" }, artist: "TOTAL OBJECTION feat.GUMI" }, { musicId: 86, title: "十面相", version: 1, category: "EXITTUNES", bpm: { min: 173, max: 173 }, chartsInfo: { light: "04", medium: "08", beast: "09⁺" }, artist: "YM feat. GUMI" }, { musicId: 87, title: "ハウトゥー世界征服", version: 1, category: "EXITTUNES", bpm: { min: 151, max: 151 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "Neru" }, { musicId: 88, title: "Just Be Friends", version: 1, category: "EXITTUNES", bpm: { min: 128, max: 128 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "Dixie Flatline" }, { musicId: 89, title: "ハッタリだけで生きてる", version: 1, category: "EXITTUNES", bpm: { min: 145, max: 145 }, chartsInfo: { light: "04", medium: "07", beast: "09⁻" }, artist: "recog feat. デッドボールP" }, { musicId: 90, title: "ってゐ! ~えいえんてゐVer~", version: 1, category: "TOHO", bpm: { min: 314, max: 340 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "石鹸屋" }, { musicId: 91, title: "東方妖々夢 ~the maximum moving about~", version: 1, category: "TOHO", bpm: { min: 197, max: 205 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "石鹸屋" }, { musicId: 92, title: "最速最高シャッターガール", version: 1, category: "TOHO", bpm: { min: 160, max: 160 }, chartsInfo: { light: "03", medium: "08", beast: "09⁺" }, artist: "ビートまりお(COOL&CREATE)" }, { musicId: 93, title: "マスパでシュッ☆メイドウィッチまりさちゃん", version: 1, category: "TOHO", bpm: { min: 160, max: 160 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "ARM feat.桃井はるこ" }, { musicId: 94, title: "進捗どうですか?", version: 1, category: "TOHO", bpm: { min: 199, max: 199 }, chartsInfo: { light: "04", medium: "08", beast: "10", nightmare: "10" }, artist: "sumijun feat.ななひら" }, { musicId: 95, title: "トラウマ催眠少女さとり!", version: 1, category: "TOHO", bpm: { min: 210, max: 210 }, chartsInfo: { light: "03", medium: "08", beast: "10", nightmare: "10" }, artist: "DJ SHARPNEL feat.一ノ瀬月琉" }, { musicId: 96, title: "きゅうりバーにダイブ", version: 1, category: "TOHO", bpm: { min: 176, max: 176 }, chartsInfo: { light: "02", medium: "07", beast: "09⁺" }, artist: "ゆずひこ feat.めらみぽっぷ" }, { musicId: 97, title: "ジャスティス・オブ・ザ・界隈 ~ALL IS FAIR IN LOVE AND ALIMARI~", version: 1, category: "TOHO", bpm: { min: 190, max: 190 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "void feat.山本椛" }, { musicId: 98, title: "EBONY & IVORY", version: 1, category: "KDE", bpm: { min: 170, max: 170 }, chartsInfo: { light: "02", medium: "08", beast: "10⁻", nightmare: "10⁺" }, artist: "OSTER project" }, { musicId: 99, title: "ツーマンライブ", version: 1, category: "KDE", bpm: { min: 174, max: 174 }, chartsInfo: { light: "02", medium: "07", beast: "09⁺", nightmare: "10-" }, artist: "東雲夏陽 meets 日向美ビタースイーツ♪" }, { musicId: 100, title: "キモチコネクト", version: 1, category: "KDE", bpm: { min: 150, max: 150 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻", nightmare: "09⁺" }, artist: "東雲心菜 meets 日向美ビタースイーツ♪" }, { musicId: 101, title: "パ→ピ→プ→Yeah!", version: 1, category: "KDE", bpm: { min: 160, max: 160 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻", nightmare: "10" }, artist: "ヒゲドライバー join. shully & Nimo" }, { musicId: 102, title: "惑星☆ロリポップ", version: 1, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "02", medium: "07", beast: "09", nightmare: "10" }, artist: "SOUND HOLIC feat. Nana Takahashi" }, { musicId: 103, title: "爆なな☆てすとロイヤー", version: 1, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "03", medium: "08", beast: "10⁻", nightmare: "10" }, artist: "ARM feat.ななひら" }, { musicId: 104, title: "Stay Gold", version: 1, category: "OTHER", bpm: { min: 190, max: 190 }, chartsInfo: { light: "02", medium: "08", beast: "10", nightmare: "10" }, artist: "A応P" }, { musicId: 105, title: "メイビ~初恋!?ビスケット☆大作戦", version: 1, category: "KDE", bpm: { min: 182, max: 182 }, chartsInfo: { light: "02", medium: "06", beast: "09", nightmare: "10-" }, artist: "DJ TOTTO×mitsu feat.BisCo(CV:洲崎綾)" }, { musicId: 106, title: "それは花火のような恋", version: 1, category: "KDE", bpm: { min: 182, max: 182 }, chartsInfo: { light: "03", medium: "07", beast: "10", nightmare: "10" }, artist: "夏色バーニングラブ☆Prim" }, { musicId: 107, title: "NEON WORLD", version: 1, category: "TOHO", bpm: { min: 154, max: 164 }, chartsInfo: { light: "02", medium: "07", beast: "09⁺" }, artist: "SOUND HOLIC feat. Nana Takahashi" }, { musicId: 108, title: "Scarlet Moon", version: 1, category: "TOHO", bpm: { min: 180, max: 180 }, chartsInfo: { light: "02", medium: "04", beast: "09⁻" }, artist: "REDALiCE feat. Ayumi Nomiya" }, { musicId: 109, title: "Russian Caravan Rhapsody", version: 1, category: "TOHO", bpm: { min: 158, max: 158 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "Power Of Nature" }, { musicId: 110, title: "ほおずき程度には赤い頭髪", version: 1, category: "TOHO", bpm: { min: 174, max: 174 }, chartsInfo: { light: "04", medium: "07", beast: "10" }, artist: "Akhuta" }, { musicId: 111, title: "取り残された美術(Arranged:HiZuMi)", version: 1, category: "TOHO", bpm: { min: 160, max: 160 }, chartsInfo: { light: "02", medium: "06", beast: "09⁺" }, artist: "幽閉サテライト feat. senya" }, { musicId: 112, title: "Struggle", version: 1, category: "TOHO", bpm: { min: 180, max: 180 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "Masayoshi Minoshima(ALR)" }, { musicId: 113, title: "ホメ猫☆センセーション", version: 1, category: "TOHO", bpm: { min: 200, max: 200 }, chartsInfo: { light: "02", medium: "07", beast: "09⁺" }, artist: "P*Light feat. mow*2" }, { musicId: 114, title: "妖隠し -あやかしかくし-", version: 1, category: "TOHO", bpm: { min: 85, max: 85 }, chartsInfo: { light: "02", medium: "06", beast: "08" }, artist: "DJ TOTTO feat.3L" }, { musicId: 115, title: "BEAT-NEW-WORLD", version: 1, category: "TOHO", bpm: { min: 190, max: 190 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10" }, artist: "ビートまりお(COOL&CREATE)" }, { musicId: 116, title: "プレインエイジア -PHQ remix-", version: 1, category: "TOHO", bpm: { min: 182, max: 182 }, chartsInfo: { light: "02", medium: "07", beast: "10⁻" }, artist: "PHQUASE" }, { musicId: 117, title: "竹取飛翔 ~ Lunatic Princess (Ryu☆Remix)", version: 1, category: "TOHO", bpm: { min: 171, max: 171 }, chartsInfo: { light: "02", medium: "06", beast: "09" }, artist: "Ryu☆" }, { musicId: 118, title: "千年ノ理", version: 1, category: "TOHO", bpm: { min: 153, max: 153 }, chartsInfo: { light: "02", medium: "07", beast: "09⁺" }, artist: "猫叉Master" }, { musicId: 119, title: "Flyers", version: 1, category: "ANIME", bpm: { min: 139, max: 139 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "BRADIO" }, { musicId: 120, title: "放課後革命", version: 1, category: "ANIME", bpm: { min: 205, max: 205 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "放課後楽園部《一宮エルナ(CV:木村珠莉)・御神楽星鎖(CV:大西沙織)・藤白おとね(CV:小澤亜李)》" }, { musicId: 121, title: "楽園ファンファーレ", version: 1, category: "ANIME", bpm: { min: 180, max: 180 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "放課後楽園部《一宮エルナ(CV:木村珠莉)・御神楽星鎖(CV:大西沙織)・藤白おとね(CV:小澤亜李)》" }, { musicId: 122, title: "FLOWER", version: 1, category: "KDE", bpm: { min: 173, max: 173 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10⁺" }, artist: "DJ YOSHITAKA" }, { musicId: 123, title: "海神", version: 1, category: "KDE", bpm: { min: 159, max: 159 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10⁺" }, artist: "兎々" }, { musicId: 124, title: "蛇神", version: 1, category: "KDE", bpm: { min: 170, max: 170 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "Zektbach" }, { musicId: 125, title: "Ha・lle・lu・jah", version: 1, category: "KDE", bpm: { min: 150, max: 150 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "SOUND HOLIC feat. Nana Takahashi" }, { musicId: 126, title: "Element of SPADA", version: 1, category: "KDE", bpm: { min: 170, max: 170 }, chartsInfo: { light: "03", medium: "06", beast: "10" }, artist: "猫叉Master feat.霜月はるか" }, { musicId: 127, title: "ジュピターガンズノベル", version: 1, category: "KDE", bpm: { min: 156, max: 156 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "工藤吉三(ベイシスケイプ)" }, { musicId: 128, title: "ヤマトなでなで♡かぐや姫", version: 1, category: "KDE", bpm: { min: 160, max: 160 }, chartsInfo: { light: "03", medium: "08", beast: "09⁺" }, artist: "ロマンチック♡Prim姫" }, { musicId: 129, title: "チョコレートスマイル", version: 1, category: "KDE", bpm: { min: 196, max: 196 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻", nightmare: "10-" }, artist: "日向美ビタースイーツ♪ & ここなつ" }, { musicId: 130, title: "さよならトリップ ~夏陽 EDM edition~", version: 1, category: "KDE", bpm: { min: 135, max: 135 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻", nightmare: "10-" }, artist: "東雲夏陽(from ここなつ)" }, { musicId: 131, title: "魔法のたまご ~心菜 ELECTRO POP edition~", version: 1, category: "KDE", bpm: { min: 142, max: 142 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺", nightmare: "10" }, artist: "東雲心菜(from ここなつ)" }, { musicId: 132, title: "怪盗BisCoの予告状!!", version: 1, category: "KDE", bpm: { min: 160, max: 160 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻", nightmare: "10" }, artist: "Qrispy Joybox" }, { musicId: 133, title: "リトライ☆ランデヴー", version: 1, category: "ANIME", bpm: { min: 175, max: 175 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "にゃ~たん(CV:村川梨衣)" }, { musicId: 134, title: "そるらる★とんちんかん", version: 1, category: "OTHER", bpm: { min: 195, max: 195 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "にゃ~たん(CV:村川梨衣)" }, { musicId: 135, title: "blue moment", version: 1, category: "ANIME", bpm: { min: 149, max: 149 }, chartsInfo: { light: "01", medium: "06", beast: "09⁻" }, artist: "ソルラルBOB" }, { musicId: 136, title: "ソルラルくれにゃ!", version: 1, category: "OTHER", bpm: { min: 185, max: 185 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "エトリオール(CV:村川梨衣、松井恵理子、花守ゆみり)" }, { musicId: 137, title: "In The Breeze", version: 1, category: "KDE", bpm: { min: 123, max: 142 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "96 & Sota ft. Mayumi Morinaga" }, { musicId: 138, title: "びいすと!", version: 1, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "04", medium: "07", beast: "09", nightmare: "10" }, artist: "ビートまりおとARM" }, { musicId: 139, title: "放課後ストライド", version: 1, category: "OTHER", bpm: { min: 231, max: 231 }, chartsInfo: { light: "04", medium: "07", beast: "10" }, artist: "一宮エルナ(CV.木村珠莉)" }, { musicId: 140, title: "革新的ヒロイズム", version: 1, category: "OTHER", bpm: { min: 153, max: 153 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "二宮シグレ(CV.島﨑信長)" }, { musicId: 141, title: "有頂天ビバーチェ", version: 1, category: "OTHER", bpm: { min: 133, max: 133 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻" }, artist: "八坂ひみ(CV.諏訪彩花)" }, { musicId: 142, title: "十六夜シーイング", version: 1, category: "OTHER", bpm: { min: 174, max: 174 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "射水アスヒ(CV.加隈亜衣)" }, { musicId: 143, title: "我楽多イノセンス", version: 1, category: "OTHER", bpm: { min: 230, max: 230 }, chartsInfo: { light: "04", medium: "08", beast: "09⁺" }, artist: "九頭竜京摩(CV.細谷佳正)" }, { musicId: 144, title: "無気力クーデター", version: 1, category: "OTHER", bpm: { min: 200, max: 200 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "赤間遊兎(CV.花江夏樹)" }, { musicId: 145, title: "花吹雪リフレクト", version: 1, category: "OTHER", bpm: { min: 118, max: 118 }, chartsInfo: { light: "02", medium: "05", beast: "08" }, artist: "湊川貞松(CV.古川慎)" }, { musicId: 146, title: "絵空事スパイラル", version: 1, category: "OTHER", bpm: { min: 197, max: 197 }, chartsInfo: { light: "05", medium: "08", beast: "09⁺" }, artist: "御神楽星鎖(CV.大西沙織)" }, { musicId: 147, title: "赤裸々キャンディ", version: 1, category: "OTHER", bpm: { min: 193, max: 193 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "藤白おとね(CV.小澤亜李)" }, { musicId: 148, title: "不条理ルーレット", version: 1, category: "OTHER", bpm: { min: 175, max: 175 }, chartsInfo: { light: "04", medium: "07", beast: "09⁺" }, artist: "ビミィ(CV.松岡禎丞)" }, { musicId: 149, title: "激メシ!!わがにゃの晩ごはん", version: 1, category: "OTHER", bpm: { min: 170, max: 170 }, chartsInfo: { light: "02", medium: "07", beast: "09⁻" }, artist: "にゃ~たん(CV:村川梨衣)、モ~たん(CV:松井恵理子)、ピヨたん(CV:佐々木未来)" }, { musicId: 150, title: "これがにゃあの生きる道", version: 1, category: "OTHER", bpm: { min: 190, max: 190 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "にゃ~たん(CV:村川梨衣)" }, { musicId: 151, title: "ラヴ・グラビティ", version: 1, category: "OTHER", bpm: { min: 180, max: 180 }, chartsInfo: { light: "02", medium: "07", beast: "09⁻" }, artist: "モ~たん(CV:松井恵理子)" }, { musicId: 152, title: "忘却の旅路", version: 1, category: "OTHER", bpm: { min: 110, max: 110 }, chartsInfo: { light: "01", medium: "06", beast: "09⁻" }, artist: "ピヨたん(CV:佐々木未来)" }, { musicId: 153, title: "ETMファイティングクライマックス! 本気の師匠チャレンジ編", version: 1, category: "OTHER", bpm: { min: 136, max: 136 }, chartsInfo: { light: "03", medium: "06", beast: "08" }, artist: "シマたん(CV:巽悠衣子)、ドラたん(CV:内田真礼)、ウリたん(CV:花守ゆみり)" }, { musicId: 154, title: "TRY! TRY! TRY!", version: 1, category: "OTHER", bpm: { min: 162, max: 162 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "シマたん(CV:巽悠衣子)" }, { musicId: 155, title: "さてこそ桃源郷", version: 1, category: "OTHER", bpm: { min: 88, max: 140 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "ドラたん(CV:内田真礼)" }, { musicId: 156, title: "激凸!?ウリティ☆ウリリズム", version: 1, category: "OTHER", bpm: { min: 216, max: 216 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10" }, artist: "ウリたん(CV:花守ゆみり)" }, { musicId: 157, title: "最強プロデュース!めざせ干支ップ☆アイドル", version: 1, category: "OTHER", bpm: { min: 70, max: 200 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "ウサたん(CV:相坂優歌)、ウマたん(CV:小澤亜李)、キーたん(CV: 戸田めぐみ)、イヌたん(CV:本多真梨子)" }, { musicId: 158, title: "がんばれ♥ ウサ☆ウサPPプロデュース!", version: 1, category: "OTHER", bpm: { min: 186, max: 186 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻" }, artist: "ウサたん(CV:相坂優歌)" }, { musicId: 159, title: "笑う門に午来る!", version: 1, category: "OTHER", bpm: { min: 172, max: 172 }, chartsInfo: { light: "02", medium: "04", beast: "08" }, artist: "ウマたん(CV:小澤亜李)" }, { musicId: 160, title: "ふたりマイウェイ!", version: 1, category: "OTHER", bpm: { min: 240, max: 240 }, chartsInfo: { light: "04", medium: "07", beast: "09⁺" }, artist: "キーたん(CV:戸田めぐみ)、イヌたん(CV:本多真梨子)" }, { musicId: 161, title: "秘湯に願いを!今夜はホット・アンド・スイート", version: 1, category: "OTHER", bpm: { min: 128, max: 128 }, chartsInfo: { light: "04", medium: "08", beast: "09⁺" }, artist: "チュウたん(CV:大原さやか)、シャアたん(CV:生天目仁美)、メイたん(CV:渕上舞)" }, { musicId: 162, title: "窮猫ハ鼠ヲモ嚙メズ", version: 1, category: "OTHER", bpm: { min: 180, max: 180 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10" }, artist: "チュウたん(CV:大原さやか)" }, { musicId: 163, title: "あなたの総集編", version: 1, category: "OTHER", bpm: { min: 160, max: 160 }, chartsInfo: { light: "02", medium: "06", beast: "08" }, artist: "シャアたん(CV:生天目仁美)" }, { musicId: 164, title: "TwinklePeace", version: 1, category: "OTHER", bpm: { min: 128, max: 128 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "メイたん(CV:渕上舞)" }, { musicId: 165, title: "隅田川夏恋歌", version: 1, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺", nightmare: "10" }, artist: "seiya-murai feat.ALT" }, { musicId: 166, title: "Sakura Sunrise", version: 1, category: "KDE", bpm: { min: 181, max: 181 }, chartsInfo: { light: "04", medium: "08", beast: "10⁻" }, artist: "Ryu☆" }, { musicId: 167, title: "虹色遊園地", version: 1, category: "KDE", bpm: { min: 165, max: 220 }, chartsInfo: { light: "03", medium: "09⁻", beast: "10" }, artist: "Mutsuhiko Izumi VS DJ TOTTO" }, { musicId: 168, title: "ドッキン☆サマーあばんちゅーる", version: 1, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "04", medium: "07", beast: "10⁻" }, artist: "L.E.D.-G ⁺ Qrispy Joybox ⁺ ARM feat. ななひら" }, { musicId: 169, title: "夏色DIARY BisCoの思い出(>▽<)", version: 1, category: "KDE", bpm: { min: 187, max: 187 }, chartsInfo: { light: "03", medium: "07", beast: "09" }, artist: "猫叉王子 feat.Qrispy Joybox" }, { musicId: 170, title: "エクストラ・マジック・アワー ", version: 1, category: "ANIME", bpm: { min: 180, max: 180 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻" }, artist: "AKINO with bless4" }, { musicId: 171, title: "SPLASH FREE", version: 1, category: "ANIME", bpm: { min: 128, max: 128 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "STYLE FIVE" }, { musicId: 172, title: "MURASAKI", version: 1, category: "OTHER", bpm: { min: 145, max: 145 }, chartsInfo: { light: "03", medium: "06", beast: "08" }, artist: "蒼井翔太" }, { musicId: 173, title: "はなまるぴっぴはよいこだけ", version: 1, category: "ANIME", bpm: { min: 172, max: 172 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺", nightmare: "10-" }, artist: "A応P" }, { musicId: 174, title: "neko*neko", version: 1, category: "KDE", bpm: { min: 123, max: 123 }, chartsInfo: { light: "03", medium: "07", beast: "09" }, artist: "日向美ビタースイーツ♪" }, { musicId: 175, title: "漆黒のスペシャルプリンセスサンデー", version: 1, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "日向美ビタースイーツ♪" }, { musicId: 176, title: "残酷な天使のテーゼ", version: 2, category: "ANIME", bpm: { min: 79, max: 128 }, chartsInfo: { light: "02", medium: "06", beast: "08" }, artist: "-" }, { musicId: 177, title: "紅蓮の弓矢", version: 2, category: "ANIME", bpm: { min: 180.66, max: 180.66 }, chartsInfo: { light: "01", medium: "06", beast: "09⁻", nightmare: "10-" }, artist: "Linked Horizon" }, { musicId: 178, title: "太陽曰く燃えよカオス", version: 2, category: "ANIME", bpm: { min: 143, max: 143 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻", nightmare: "10" }, artist: "後ろから這いより隊G" }, { musicId: 179, title: "Enigmatic Feeling", version: 2, category: "ANIME", bpm: { min: 142, max: 142 }, chartsInfo: { light: "02", medium: "06", beast: "09⁺" }, artist: "凛として時雨" }, { musicId: 180, title: "シュガーソングとビターステップ", version: 2, category: "ANIME", bpm: { min: 132, max: 132 }, chartsInfo: { light: "02", medium: "07", beast: "09⁺", nightmare: "10" }, artist: "UNISON SQUARE GARDEN" }, { musicId: 181, title: "COLORFUL BOX ", version: 2, category: "ANIME", bpm: { min: 161, max: 161 }, chartsInfo: { light: "03", medium: "07", beast: "09⁻" }, artist: "石田燿子" }, { musicId: 182, title: "宝箱-TREASURE BOX-", version: 2, category: "ANIME", bpm: { min: 162, max: 162 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "奥井雅美" }, { musicId: 183, title: "かくしん的☆めたまるふぉ〜ぜっ!", version: 2, category: "ANIME", bpm: { min: 133, max: 139 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺", nightmare: "10" }, artist: "土間うまる(CV:田中あいみ)" }, { musicId: 184, title: "Twinbee's Home Town Song", version: 2, category: "KDE", bpm: { min: 168, max: 168 }, chartsInfo: { light: "04", medium: "07", beast: "09⁺" }, artist: "Sota Fujimori" }, { musicId: 185, title: "トキメキストリーム", version: 2, category: "KDE", bpm: { min: 164, max: 164 }, chartsInfo: { light: "02", medium: "07", beast: "10", nightmare: "10⁺" }, artist: "Qrispy Joybox" }, { musicId: 186, title: "にゃんのパレードマーチ♪", version: 2, category: "KDE", bpm: { min: 162, max: 162 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "DJ TOTTO feat.にゃん" }, { musicId: 187, title: "アキネイション", version: 2, category: "KDE", bpm: { min: 185, max: 185 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "ビートまりお(COOL&CREATE)" }, { musicId: 188, title: "秘密がーる♡乙女", version: 2, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "03", medium: "07", beast: "09" }, artist: "MaMiKa(松下×mitsu×T.kakuta)" }, { musicId: 189, title: "ぱんだしんけん1、2、3 ~ちえ!おっしょさんにはかなわないや!~", version: 2, category: "KDE", bpm: { min: 158, max: 158 }, chartsInfo: { light: "03", medium: "08", beast: "09⁺" }, artist: "おひさまくらぶ" }, { musicId: 190, title: "蟲の棲む処", version: 2, category: "KDE", bpm: { min: 161.51, max: 161.51 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "かめりあ feat. Nana Takahashi" }, { musicId: 191, title: "激アツ☆マジヤバ☆チアガール", version: 2, category: "KDE", bpm: { min: 170, max: 170 }, chartsInfo: { light: "03", medium: "07", beast: "08" }, artist: "日向美ビタースイーツ♪" }, { musicId: 192, title: "打打打打打打打打打打", version: 2, category: "KDE", bpm: { min: 156, max: 156 }, chartsInfo: { light: "03", medium: "05", beast: "10⁻", nightmare: "10" }, artist: "ヒゲドライバー join. SELEN" }, { musicId: 193, title: "創聖のアクエリオン", version: 2, category: "ANIME", bpm: { min: 151, max: 151 }, chartsInfo: { light: "01", medium: "05", beast: "08" }, artist: "-" }, { musicId: 194, title: "RPG", version: 2, category: "ANIME", bpm: { min: 135, max: 135 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "SEKAI NO OWARI" }, { musicId: 195, title: "患部で止まってすぐ溶ける ~ 狂気の優曇華院", version: 2, category: "TOHO", bpm: { min: 200, max: 200 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻" }, artist: "ARM(IOSYS)" }, { musicId: 196, title: "Grip & Break down !!", version: 2, category: "TOHO", bpm: { min: 160, max: 160 }, chartsInfo: { light: "04", medium: "07", beast: "10⁻", nightmare: "10" }, artist: "SOUND HOLIC feat. Nana Takahashi" }, { musicId: 197, title: "究極焼肉レストラン!お燐の地獄亭!", version: 2, category: "TOHO", bpm: { min: 185, max: 185 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "ARM(IOSYS)" }, { musicId: 198, title: "楽しい夜のお茶会 - Ringo's Tea Party", version: 2, category: "TOHO", bpm: { min: 175, max: 175 }, chartsInfo: { light: "03", medium: "05", beast: "09⁻" }, artist: "ARM(IOSYS)" }, { musicId: 199, title: "幻想のサテライト", version: 2, category: "TOHO", bpm: { min: 230, max: 230 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "豚乙女" }, { musicId: 200, title: "Wheel", version: 2, category: "TOHO", bpm: { min: 136, max: 136 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "Syrufit feat.綾倉盟 / 市松椿" }, { musicId: 201, title: "Vampire Killer", version: 2, category: "KDE", bpm: { min: 128, max: 128 }, chartsInfo: { light: "03", medium: "08", beast: "10" }, artist: "浅田 靖" }, { musicId: 202, title: "GRADIUS 2012", version: 2, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "05", medium: "09⁻", beast: "10" }, artist: "Sota Fujimori" }, { musicId: 203, title: "月風魔伝 ~ ビーストメドレー ~", version: 2, category: "KDE", bpm: { min: 170, max: 170 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10" }, artist: "Akhuta" }, { musicId: 204, title: "がんばれゴエモン ~ ビーストメドレー ~", version: 2, category: "KDE", bpm: { min: 113, max: 190 }, chartsInfo: { light: "02", medium: "06", beast: "09⁻" }, artist: "96" }, { musicId: 205, title: "分けるな危険!モモモモモモーイズム", version: 2, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "04", medium: "08", beast: "10⁻" }, artist: "ARM×狐夢想 feat. 桃井はるこ" }, { musicId: 206, title: "CARNIVOROUS", version: 2, category: "KDE", bpm: { min: 170, max: 170 }, chartsInfo: { light: "05", medium: "09⁻", beast: "10" }, artist: "SOUND HOLIC feat. Nana Takahashi" }, { musicId: 207, title: "地方創生☆チクワクティクス", version: 2, category: "KDE", bpm: { min: 170, max: 170 }, chartsInfo: { light: "04", medium: "07", beast: "10" }, artist: "日向美ビタースイーツ♪" }, { musicId: 208, title: "リリーゼと炎龍レーヴァテイン", version: 2, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "05", medium: "08", beast: "10", nightmare: "10⁺" }, artist: "黒猫ダンジョン" }, { musicId: 209, title: "smooooch・∀・", version: 2, category: "KDE", bpm: { min: 177, max: 177 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "kors k" }, { musicId: 210, title: "Twinkle Wonderland", version: 2, category: "KDE", bpm: { min: 188, max: 188 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻" }, artist: "Qrispy Joybox feat.Sana " }, { musicId: 211, title: "She is my wife", version: 2, category: "KDE", bpm: { min: 85, max: 170 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "SUPER STAR 満-MITSURU-" }, { musicId: 212, title: "ほしふり", version: 2, category: "KDE", bpm: { min: 155, max: 155 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "Sana" }, { musicId: 213, title: "梅雪夜", version: 2, category: "KDE", bpm: { min: 167, max: 167 }, chartsInfo: { light: "04", medium: "07", beast: "10", nightmare: "10⁺" }, artist: "Qrispy Joybox feat.mao" }, { musicId: 214, title: "絶品☆みこみこミラクル昇天レシピ!", version: 2, category: "OTHER", bpm: { min: 204, max: 204 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "MOSAIC.WAV & miko(Alternative ending)" }, { musicId: 215, title: "GRADIUS Ⅱ ~ ビーストメドレー ~", version: 2, category: "KDE", bpm: { min: 190, max: 190 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "Sota Fujimori" }, { musicId: 216, title: "キミとワタシのオンガク", version: 2, category: "KDE", bpm: { min: 160, max: 160 }, chartsInfo: { light: "02", medium: "06", beast: "10⁻" }, artist: "seiya-murai feat. ALT" }, { musicId: 217, title: "量子の海のリントヴルム", version: 2, category: "KDE", bpm: { min: 280, max: 280 }, chartsInfo: { light: "05", medium: "09", beast: "10", nightmare: "10⁺" }, artist: "黒猫ダンジョン" }, { musicId: 218, title: "混ぜるな危険", version: 2, category: "ANIME", bpm: { min: 178, max: 178 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "筋肉少女帯" }, { musicId: 219, title: "Sky High", version: 2, category: "KDE", bpm: { min: 153, max: 153 }, chartsInfo: { light: "05", medium: "09⁺", beast: "10⁺", nightmare: "ネ申" }, artist: "Cuvelia" }, { musicId: 220, title: "SPACE VILLAGE", version: 2, category: "KDE", bpm: { min: 170, max: 170 }, chartsInfo: { light: "02", medium: "06", beast: "10⁻" }, artist: "サイバー劇レコ" }, { musicId: 221, title: "全力バタンキュー", version: 2, category: "ANIME", bpm: { min: 155, max: 155 }, chartsInfo: { light: "03", medium: "06", beast: "09" }, artist: "A応P" }, { musicId: 222, title: "きゅん×きゅんばっきゅん☆LOVE", version: 2, category: "KDE", bpm: { min: 165, max: 165 }, chartsInfo: { light: "03", medium: "06", beast: "09" }, artist: "松下feat.Sota & wac" }, { musicId: 223, title: "CHERNOBOG", version: 2, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "05", medium: "09", beast: "10⁺", nightmare: "ネ申" }, artist: "漆黒のEBONY" }, { musicId: 224, title: "spring pony", version: 2, category: "KDE", bpm: { min: 150, max: 150 }, chartsInfo: { light: "02", medium: "06", beast: "09" }, artist: "S-C-U" }, { musicId: 225, title: "繚乱ヒットチャート", version: 2, category: "KDE", bpm: { min: 192, max: 192 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻" }, artist: "ギラギラメガネ団" }, { musicId: 226, title: "Spring Comes Around (In Like a Lion)", version: 2, category: "KDE", bpm: { min: 159.5, max: 159.5 }, chartsInfo: { light: "03", medium: "07", beast: "09" }, artist: "Sota Fujimori Rmx by wac" }, { musicId: 227, title: "アルストロメリア", version: 2, category: "KDE", bpm: { min: 144, max: 144 }, chartsInfo: { light: "04", medium: "08", beast: "10" }, artist: "TAG" }, { musicId: 228, title: "Sakura Reflection", version: 2, category: "KDE", bpm: { min: 181, max: 181 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "Ryu☆" }, { musicId: 229, title: "黒髪乱れし修羅となりて~凛 edition~", version: 2, category: "KDE", bpm: { min: 300, max: 300 }, chartsInfo: { light: "04", medium: "06", beast: "09⁺", nightmare: "10" }, artist: "日向美ビタースイーツ♪" }, { musicId: 230, title: "Clattanoia", version: 2, category: "ANIME", bpm: { min: 190, max: 190 }, chartsInfo: { light: "03", medium: "07", beast: "09" }, artist: "OxT" }, { musicId: 231, title: "クローバー♣かくめーしょん", version: 2, category: "ANIME", bpm: { min: 145, max: 145 }, chartsInfo: { light: "02", medium: "05", beast: "08" }, artist: "とりぷる♣ふぃーりんぐ(和久井 優/金澤まい/今村彩夏)" }, { musicId: 232, title: "Knew day", version: 2, category: "ANIME", bpm: { min: 150, max: 150 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "(K)NoW_NAME" }, { musicId: 233, title: "からくりピエロ", version: 2, category: "EXITTUNES", bpm: { min: 102, max: 102 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "40mP" }, { musicId: 234, title: "華鳥風月", version: 2, category: "TOHO", bpm: { min: 123, max: 123 }, chartsInfo: { light: "02", medium: "05", beast: "09⁻" }, artist: "幽閉サテライト" }, { musicId: 235, title: "人間が大好きなこわれた妖怪の唄", version: 2, category: "TOHO", bpm: { min: 166, max: 166 }, chartsInfo: { light: "03", medium: "06", beast: "09⁻" }, artist: "ビートまりお(COOL&CREATE)" }, { musicId: 236, title: "忌憚騒命曲 ~ Demon tale sonata", version: 2, category: "TOHO", bpm: { min: 185, max: 185 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "どぶウサギ(dBu music)" }, { musicId: 237, title: "キャプテン・ムラサのケツアンカー", version: 2, category: "TOHO", bpm: { min: 190, max: 190 }, chartsInfo: { light: "04", medium: "07", beast: "09⁺" }, artist: "ARM⁺夕野ヨシミ feat.山本椛" }, { musicId: 238, title: "Power of Battle(vs REDALiCE)", version: 2, category: "TOHO", bpm: { min: 178, max: 178 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "RoughSketch vs REDALiCE feat.イザベル" }, { musicId: 239, title: "鈴瑚のお団子ジャングルジム", version: 2, category: "TOHO", bpm: { min: 190, max: 190 }, chartsInfo: { light: "03", medium: "07", beast: "09" }, artist: "ARM⁺狐夢想 feat.ななひら" }, { musicId: 240, title: "wandering to sweet", version: 2, category: "TOHO", bpm: { min: 154, max: 154 }, chartsInfo: { light: "03", medium: "05", beast: "09⁻" }, artist: "平茸⁺夕野ヨシミ feat.山本椛" }, { musicId: 241, title: "Lost wing at.0", version: 2, category: "KDE", bpm: { min: 185, max: 185 }, chartsInfo: { light: "04", medium: "07", beast: "10" }, artist: "猫叉Master⁺" }, { musicId: 242, title: "ロプノールの商隊", version: 2, category: "KDE", bpm: { min: 186, max: 186 }, chartsInfo: { light: "02", medium: "06", beast: "10" }, artist: "Akhuta" }, { musicId: 243, title: "Phlox", version: 2, category: "KDE", bpm: { min: 185, max: 185 }, chartsInfo: { light: "04", medium: "08", beast: "10⁻" }, artist: "Sota Fujimori 2nd Season" }, { musicId: 244, title: "Dynasty", version: 2, category: "KDE", bpm: { min: 189, max: 189 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "Yooh" }, { musicId: 245, title: "じゅーじゅー♥焼肉の火からフェニックス!?~再誕の†炭火焼き~", version: 2, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "04", medium: "09⁻", beast: "10" }, artist: "かめりあ feat. ななひら" }, { musicId: 246, title: "雪女", version: 2, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "02", medium: "07", beast: "10" }, artist: "かねこちはる" }, { musicId: 247, title: "フラッター現象の顛末と単一指向性の感情論", version: 2, category: "KDE", bpm: { min: 206, max: 206 }, chartsInfo: { light: "02", medium: "06", beast: "09" }, artist: "日向美ビタースイーツ♪" }, { musicId: 248, title: "Good bye, Summer~さよならは言わない~", version: 2, category: "KDE", bpm: { min: 165, max: 165 }, chartsInfo: { light: "03", medium: "06", beast: "09" }, artist: "私立BEMANI学園軽音部 OB" }, { musicId: 249, title: "スカイダイバー", version: 2, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "05", medium: "07", beast: "09" }, artist: "ヒゲドライバー feat.ヒゲドライVAN" }, { musicId: 250, title: "週替わりの奇跡の神話", version: 2, category: "ANIME", bpm: { min: 169, max: 169 }, chartsInfo: { light: "02", medium: "06", beast: "09" }, artist: "筋肉少女帯" }, { musicId: 251, title: "GO! GO! MANIAC", version: 2, category: "ANIME", bpm: { min: 250, max: 250 }, chartsInfo: { light: "04", medium: "08", beast: "09⁺" }, artist: "放課後ティータイム" }, { musicId: 252, title: "最高速 Fall in Love", version: 2, category: "ANIME", bpm: { min: 132, max: 175 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻" }, artist: "ミーア、パピ、セントレア、スー、メロ、ラクネラ" }, { musicId: 253, title: "ハッピーシンセサイザ", version: 2, category: "OTHER", bpm: { min: 127, max: 127 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "EasyPop" }, { musicId: 254, title: "東京テディベア", version: 2, category: "OTHER", bpm: { min: 204, max: 204 }, chartsInfo: { light: "03", medium: "07", beast: "09⁺" }, artist: "Neru" }, { musicId: 255, title: "ケンぱ!ケンぱ!拳拳ぱん打!", version: 2, category: "KDE", bpm: { min: 200, max: 200 }, chartsInfo: { light: "03", medium: "06", beast: "10⁻" }, artist: "ぱんぱんぱん打" }, { musicId: 256, title: "Lost wing at.0", version: 2, category: "KDE", bpm: { min: 185, max: 185 }, chartsInfo: { light: "04", medium: "07", beast: "10" }, artist: "猫叉Master⁺" }, { musicId: 257, title: "朧", version: 2, category: "KDE", bpm: { min: 149, max: 149 }, chartsInfo: { light: "03", medium: "07", beast: "10" }, artist: "HHH×MM×ST" }, { musicId: 258, title: "恋はどう?モロ◎波動OK☆方程式!!", version: 2, category: "KDE", bpm: { min: 128, max: 128 }, chartsInfo: { light: "05", medium: "09⁻", beast: "10" }, artist: "あべにゅうぷろじぇくと feat.佐倉紗織 produced by ave;new" }, { musicId: 259, title: "恋歌疾風!かるたクイーンいろは", version: 2, category: "KDE", bpm: { min: 120, max: 168 }, chartsInfo: { light: "04", medium: "09", beast: "10⁺" }, artist: "ねこまんまチーム!" }, { musicId: 260, title: "SAKURAスキップ", version: 2, category: "ANIME", bpm: { min: 165, max: 165 }, chartsInfo: { light: "03", medium: "06", beast: "09" }, artist: "fourfolium" }, { musicId: 261, title: "Now Loading!!!!", version: 2, category: "ANIME", bpm: { min: 193, max: 193 }, chartsInfo: { light: "03", medium: "05", beast: "09⁻" }, artist: "fourfolium" }, { musicId: 262, title: "カケラ", version: 2, category: "KDE", bpm: { min: 182, max: 182 }, chartsInfo: { light: "05", medium: "07", beast: "09⁺" }, artist: "豚乙女" }, { musicId: 263, title: "怒りと共に去りぬ!!", version: 2, category: "KDE", bpm: { min: 190, max: 190 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻" }, artist: "96 feat.すわひでお" }, { musicId: 264, title: "在るが儘に", version: 2, category: "KDE", bpm: { min: 181, max: 181 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "Des-ROW・組スペシアルr" }, { musicId: 265, title: "Daisuke", version: 2, category: "KDE", bpm: { min: 157, max: 157 }, chartsInfo: { light: "04", medium: "07", beast: "10" }, artist: "Y&Co." }, { musicId: 266, title: "JOMANDA", version: 2, category: "KDE", bpm: { min: 90, max: 300 }, chartsInfo: { light: "05", medium: "09", beast: "10⁺" }, artist: "DJ YOSHITAKA" }, { musicId: 267, title: "サヨナラ・ヘヴン", version: 2, category: "KDE", bpm: { min: 111, max: 111 }, chartsInfo: { light: "02", medium: "06", beast: "09⁺" }, artist: "猫叉Master" }, { musicId: 268, title: "轟け!恋のビーンボール!!", version: 2, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "03", medium: "07", beast: "10⁻", nightmare: "10⁺" }, artist: "ダイナミック野球兄弟 v.s. クロスファイヤーPrim" }, { musicId: 269, title: "少年リップルズ", version: 2, category: "KDE", bpm: { min: 210, max: 210 }, chartsInfo: { light: "04", medium: "07", beast: "10⁻" }, artist: "常盤ゆう" }, { musicId: 270, title: "たまゆら", version: 2, category: "KDE", bpm: { min: 180, max: 180 }, chartsInfo: { light: "04", medium: "06", beast: "09⁺" }, artist: "佐々木博史" }, { musicId: 271, title: "quaver♪", version: 2, category: "KDE", bpm: { min: 182, max: 186 }, chartsInfo: { light: "05", medium: "08", beast: "10⁺" }, artist: "Risk Junk" }, { musicId: 272, title: "SigSig", version: 2, category: "KDE", bpm: { min: 179, max: 179 }, chartsInfo: { light: "03", medium: "06", beast: "09⁺" }, artist: "kors k" }] + let courseTitle = ["RANK 01","RANK 02","RANK 03","RANK 04","RANK 05","RANK 06","RANK 07","RANK 08","RANK 09","RANK 10","RANK 11","RANK 12","RANK 13","RANK 14","RANK 15","RANK ネ申"] + let bstChartTypeDetails = [{title:"Light",color:"#2EBECC",colorInvert:"#2EBECC"},{title:"Medium",color:"#F2C40B",colorInvert:"#F2C40B"},{title:"Beast",color:"#FF3860",colorInvert:"#FF3860"},{title:"Nightmare",color:"#000000",colorInvert:"#00DDDD"},] + + let defaultPageSize = 20 + +//- Processing +- + bst2Course.sort((l, r) => r.courseId - l.courseId) + bst2StageLog.sort((l, r) => r.time - l.time) + if (bst2Account != null) { + let checkUserId = (col) => { + let removeList = [] + for (i = 0; i < col.length; i++) if (col[i].userId != bst2Account.userId) removeList.push(i) + for (let i of removeList) col.splice(i, 1) + } + checkUserId(bst2Course) + checkUserId(bst2MusicRecord) + checkUserId(bst2CourseLog) + checkUserId(bst2StageLog) + } + + let bst2MusicRecordOrganized = [] + for (let r of bst2MusicRecord) { + let o = bst2MusicRecordOrganized[r.musicId] + if (o == null) o = {} + o[r.chart] = r + bst2MusicRecordOrganized[r.musicId] = o + } + +//- Play data +- + let bst2HighestRank = -1 + for (let r of bst2Course) if ((r.courseId > bst2HighestRank) && (r.medal >= 3)) bst2HighestRank = r.courseId + function getClearTypeDetails(record) { + if (record.medal == 5) return { title: "Perfect", abbr: "P", color: "#FF3860", badge: "mdi mdi-weather-sunny mdi-48px" } + else if (record.medal == 4) return { title: "Full Combo", abbr: "FC", color: "gold", badge: "mdi mdi-cog mdi-48px" } + else if (record.medal == 3) return { title: "Clear", abbr: "C", color: "#35F2BE", badge: "mdi mdi-star-circle-outline mdi-48px"} + else if ((record.medal == 2) || (record.medal == 1)) return { title: "Failed", abbr: "F", color: "#837E8D", badge: "mdi mdi-close-circle-outline mdi-48px"} + else return null + } + function getRank(score, version) { + if ((score >= 970000) && (version == 2)) return { title: "AAA", color: "#FF3860", colorInvert: "#FF3860" } + else if (score >= 950000) return { title: "AAA", color: "black", colorInvert: "gold" } + else if (score >= 850000) return { title: "AA", color: "black", colorInvert: "gold" } + else if (score >= 700000) return { title: "A", color: "black", colorInvert: "gold" } + else if (score >= 600000) return { title: "B", color: "black", colorInvert: "#40FEAA" } + else if (score >= 500000) return { title: "C", color: "black", colorInvert: "#40FEAA" } + else return { title: "D", color: "black", colorInvert: "#837E8D" } + } + +link(rel="stylesheet", href="static/css/webui_util.css", type="text/css") +script(src = "static/js/webui_util.js") +.modal.loading.is-active + .modal-background(style = "opacity: 50%") + p(style = "position: fixed; bottom: 40px; right: 40px; color: white") + span Now Loading... + i.mdi.mdi-refresh-circle.mdi-spin +div + if (webuiMessage != null) && ((webuiMessage.refid == null) || (webuiMessage.refid == refid)) + div(class = "notification temporary" + ((webuiMessage.type == 0) ? " is-info" : (webuiMessage.type == 1) ? " is-success" : " is-danger"), style = "display: block") + .delete(style = "margin-top: 10px") + span #{webuiMessage.message} + form.start(method="post" action="/emit/removeWebUIMessage", style = "display: none") + .columns.is-mobile.is-vcentered(style = "overflow-x: auto") + .column + .column.is-narrow + p.is-size-7 Select game version + .column.is-narrow + style. + .tabs.is-toggle li a, .tabs.is-toggle li a span, .tabs.is-toggle li a span span, .tabs.is-toggle li a div, .tabs.is-toggle li a .background { + transition: .2s linear, font-weight 0s linear; + } + .tabs.is-toggle ul li.is-active[tab-group="version"][tab-index="2"] a { + background-color: black; + color: white; + font-weight: bold; + text-shadow: 0 1.5px 0 #D0D09A; + border-color: black; + } + .tabs.is-toggle ul li.is-active[tab-group="version"][tab-index="2"] a span { + transform: skewX(-12deg); + } + .tabs.is-toggle.is-toggle-rounded.is-small#tabs + ul(style = "margin: 0") + li.disabled(tab-group = "version", tab-index = "1", style = "width: 112px", title = "Coming s∞n...") + a BeatStream + li(tab-group = "version", tab-index = "2", style = "width: 112px", class = (defaultVersion == 2) ? "is-active" : "") + a + span アニムトライヴ + .card + header.card-header + p.card-header-title + span.icon + i.mdi.mdi-account-circle + | Play Data + a.card-header-icon.card-toggle(card = "play-data") + span.icon + i.mdi.mdi-chevron-down + .card-content(card = "play-data") + #tab-content(tab-group = "version", tab-index = "2", class = (defaultVersion == 2) ? "is-active" : "") + if bst2Account == null + .has-text-grey.has-text-centered + p + span.icon.is-large + i.mdi.mdi-emoticon-happy.mdi-48px + p Nothing's here yet. Let's play! + else + .tabs#tabs + ul + li.is-active(tab-group = "bst2-play-data", tab-index = "0") + a Overview + li(tab-group = "bst2-play-data", tab-index = "1") + a Scores + li(tab-group = "bst2-play-data", tab-index = "2") + a Course + li(tab-group = "bst2-play-data", tab-index = "3") + a Play History + #tab-content.is-active(tab-group = "bst2-play-data", tab-index = "0") + .field.is-horizontal + .field-label + label.label User ID + .field-body + span #{bst2Account.userId.toString().padStart(8, "0").slice(0, 4)}-#{bst2Account.userId.toString().padStart(8, "0").slice(4, 8)} + .field.is-horizontal + .field-label + label.label Name + .field-body + span #{bst2Base.name} + .field.is-horizontal + .field-label + label.label Play Count + .field-body + span #{bst2Account.playCount} + .field.is-horizontal + .field-label + label.label Beast Rank + .field-body + if bst2Base.brnk == 0 + span - + else + span #{courseTitle[bst2Base.brnk - 1]} + #tab-content(tab-group = "bst2-play-data", tab-index = "1", style = "overflow-x: auto; overflow-y: hidden") + if bst2MusicRecord.length == 0 + .has-text-grey.has-text-centered + p + span.icon.is-large + i.mdi.mdi-emoticon-happy.mdi-48px + p Nothing's here yet. Let's play! + else + .paginated-container(pagination-group = "bst2-scores") + input.page-size(type = "hidden", value = defaultPageSize) + .hidden-wrapper + .pagination + a.pagination-previous + i.mdi.mdi-chevron-left + | Previous + a.pagination-next + | Next + i.mdi.mdi-chevron-right + ul.pagination-list#tabs + .scrolling-x-wrapper.hidden-y-wrapper + table.table(style = "vertical-align: middle") + thead + tr + th ID + th(stye = "width: 250px") Title + th Scores + tbody + each r in bst2MusicRecordOrganized + if r != null + - let easiestChart = 4 + - for (let k in r) if ((parseInt(k).toString() == k) && (parseInt(k) < easiestChart)) easiestChart = parseInt(k) + - let info = bstMusicsInfo[r[easiestChart].musicId] + tr.is-vcentered.paginated-content + td(style = "white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle;") #{r[easiestChart].musicId} + td(style = "width: 250px; vertical-align: middle") + .marquee-label-container(style = "max-width: 250px;") + span.modal-trigger + a.marquee-label(style = "display: inline-block") #{info.title} + .modal.is-mobile + .modal-background.is-mobile#close + .modal-card.is-mobile(style = "vertical-align: middle; margin-top: 65px") + header.modal-card-head + p.modal-card-title(style = "margin-bottom: 0") Details + button.delete#close + section.modal-card-body + table.table + thead + tr + th + each i in [0, 1, 2, 3] + th(style = "width: 120px; color: " + bstChartTypeDetails[i].color) #{bstChartTypeDetails[i].title} + tbody + tr + th Music ID + td(colspan = "4") #{info.musicId} + tr + th Title + td(colspan = "4", style = "white-space: normal;") #{info.title} + tr + th Artist + td(colspan = "4", style = "white-space: normal;") #{info.artist} + tr + th BPM + td(colspan = "4") #{info.bpm.min + ((info.bpm.max == info.bpm.min) ? "" : (" - " + info.bpm.max))} + tr + th Level + each i in [0, 1, 2, 3] + - let t = bstChartTypeDetails[i] + if info.chartsInfo[t.title.toLowerCase()] != null + td(style = "color: " + ((info.chartsInfo[t.title.toLowerCase()] == "ネ申") ? "gold; text-shadow: 0 0 3px gold;" : t.color)) #{info.chartsInfo[t.title.toLowerCase()]} + else + td + tr + th State + each i in [0, 1, 2, 3] + if (r[i] != null) && (getClearTypeDetails(r[i]) != null) + - let c = r[i] + - let d = getClearTypeDetails(c) + - let t = bstChartTypeDetails[c.chartType] + td + abbr(title = d.title) #{d.abbr} + else + td + tr + th Rank + each i in [0, 1, 2, 3] + if (r[i] != null) && (getClearTypeDetails(r[i]) != null) + - let c = r[i] + - let d = getClearTypeDetails(c) + - let t = bstChartTypeDetails[c.chartType] + - let rank = getRank(c.score, 2) + td(style = "color: " + rank.color) #{rank.title} + else + td + tr + th Score + each i in [0, 1, 2, 3] + if (r[i] != null) && (getClearTypeDetails(r[i]) != null) + - let c = r[i] + - let d = getClearTypeDetails(c) + - let t = bstChartTypeDetails[c.chartType] + td #{c.score} + else + td + tr + th Combo + each i in [0, 1, 2, 3] + if (r[i] != null) && (getClearTypeDetails(r[i]) != null) + - let c = r[i] + - let d = getClearTypeDetails(c) + - let t = bstChartTypeDetails[c.chartType] + td #{c.combo} + else + td + tr + th Update + each i in [0, 1, 2, 3] + if (r[i] != null) && (getClearTypeDetails(r[i]) != null) + - let c = r[i] + - let d = getClearTypeDetails(c) + - let t = bstChartTypeDetails[c.chartType] + td #{new Date(c.updateTime).toLocaleString().split(" ")[0]} + else + td + tr + th Last Play + each i in [0, 1, 2, 3] + if (r[i] != null) && (getClearTypeDetails(r[i]) != null) + - let c = r[i] + - let d = getClearTypeDetails(c) + - let t = bstChartTypeDetails[c.chartType] + td #{new Date(c.lastPlayTime).toLocaleString().split(" ")[0]} + else + td + footer.modal-card-foot + td + .columns.is-mobile + each i in [0, 1, 2, 3] + if (r[i] != null) && (getClearTypeDetails(r[i]) != null) + - let c = r[i] + - let d = getClearTypeDetails(c) + - let t = bstChartTypeDetails[c.chart] + .column.is-one-quarter.modal-trigger(style = "vertical-align: middle;") + a.one-quarter#forwide(style = "min-width: 120px") + .heading.has-text-centered(style = "white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: " + t.color) #{t.title} + .title.has-text-centered(style = "white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: " + t.color) #{c.score} + a.one-quarter.has-text-centered#fornarrow(style = "white-space: nowrap; overflow: hidden; min-width: 60px; color: " + t.color) #{c.score} + .modal.is-mobile + .modal-background.is-mobile#close + .modal-card.is-mobile(style = "vertical-align: middle; margin-top: 65px") + header.modal-card-head + p.modal-card-title(style = "margin-bottom: 0") Details + button.delete#close + section.modal-card-body + .form + .field.is-horizontal + .field-label + label.label Music ID + .field-body + span #{c.musicId} + .field.is-horizontal + .field-label + label.label Music Title + .field-body + span #{info.title} + .field.is-horizontal + .field-label + label.label Artist + .field-body + span #{info.artist} + .field.is-horizontal + .field-label + label.label BPM + .field-body + span #{info.bpm.min + ((info.bpm.max == info.bpm.min) ? "" : (" - " + info.bpm.max))} + .field.is-horizontal + .field-label + label.label Chart + .field-body + .level.is-mobile + .level-left + .level-item + span(style = "color: " + t.color) #{t.title} + .level-item + p(style = "color: " + ((info.chartsInfo[t.title.toLowerCase()] == "ネ申") ? "gold; text-shadow: 0 0 3px gold;" : t.color)) #{info.chartsInfo[t.title.toLowerCase()]} + .field.is-horizontal + .field-label + label.label State + .field-body + span #{d.title} + .field.is-horizontal + .field-label + label.label Rank + .field-body + span(style = "color: " + getRank(c.score, 2).color) #{getRank(c.score, 2).title} + .field.is-horizontal + .field-label + label.label Score + .field-body + span #{c.score} + .field.is-horizontal + .field-label + label.label Combo + .field-body + span #{c.combo} + .field.is-horizontal + .field-label + label.label(style = "white-space: nowrap; vertical-align: middle;") Update Time + .field-body + span #{new Date(c.updateTime).toLocaleString()} + .field.is-horizontal + .field-label + label.label(style = "white-space: nowrap; vertical-align: middle;") Last Play + .field-body + span #{new Date(c.lastPlayTime).toLocaleString()} + footer.modal-card-foot + else + .column.is-one-quarter + a.one-quarter#forwide(style = "min-width: 120px") + a.one-quarter#fornarrow(style = "min-width: 120px") + .hidden-wrapper + .pagination + a.pagination-previous + i.mdi.mdi-chevron-left + | Previous + a.pagination-next + | Next + i.mdi.mdi-chevron-right + ul.pagination-list#tabs + #tab-content(tab-group = "bst2-play-data", tab-index = "2", style = "overflow-x: auto") + if bst2Course.length == 0 + .has-text-grey.has-text-centered + p + span.icon.is-large + i.mdi.mdi-emoticon-happy.mdi-48px + p Nothing's here yet. Let's play! + else + table.table + thead + tr + th Course + th State + th Score + th Combo + th Gauge + th Play Count + th Update Time + th Last Play Time + tbody + each c in bst2Course + tr + td(style = "white-space: nowrap;") #{courseTitle[c.courseId]} + td #{getClearTypeDetails(c).title} + td #{c.score} + td #{c.combo} + td #{(c.gauge / 10).toFixed(1)}% + td #{c.playCount} + td #{new Date(c.updateTime).toLocaleString()} + td #{new Date(c.lastPlayTime).toLocaleString()} + #tab-content(tab-group = "bst2-play-data", tab-index = "3", style = "overflow-x: auto") + if bst2StageLog.length == 0 + .has-text-grey.has-text-centered + p + span.icon.is-large + i.mdi.mdi-emoticon-happy.mdi-48px + p Nothing's here yet. Let's play! + else + .paginated-container(pagination-group = "bst2-play-history") + input.page-size(type = "hidden", value = defaultPageSize) + .hidden-wrapper + .pagination + a.pagination-previous + i.mdi.mdi-chevron-left + | Newer + a.pagination-next + | Older + i.mdi.mdi-chevron-right + ul.pagination-list#tabs + .scrolling-wrapper + table.table + thead + th ID + th(style = "max-width: 200px") Title + th Chart + th Level + th State + th Score + th Scene + th Time + tbody + - + let progresspattern = "" + for (i = 0; i <= 24; i++) progresspattern += "transparent " + (i * 4 + 2) + "%, #FFFFFF4F " + (i * 4 + 2) + "%, #FFFFFF4F " + (i * 4 + 3) + "%, transparent " + (i * 4 + 3) + ((i != 24) ? "%, " : "%") + each s in bst2StageLog + - let info = bstMusicsInfo[s.musicId] + - let chart = bstChartTypeDetails[s.chart] + - let level = info.chartsInfo[chart.title.toLowerCase()] + - let rank = getRank(s.score, 2) + - let clearType = getClearTypeDetails(s) + tr.paginated-content + td #{s.musicId} + td(style = "max-width: 250px") + .marquee-label-container(style = "max-width: 250px;") + span.modal-trigger + a.marquee-label #{info.title} + .modal.is-mobile(style = "x-overflow: auto;") + .modal-background.is-mobile#close + .modal-content + .box(style = "background-color: black; color: white; border-radius: 12px; min-width: 600px") + p.has-text-centered(style = "border-bottom: 3px solid grey") #{info.title} + .columns.is-mobile + .column.is-narrow + .has-text-centered(style = "width: 256px; height: 256px; background-color: lightgrey; color: darkgray; border-radius: 3%; border: 3px solid " + chart.colorInvert) + p Should be a jacket here... + .level.is-mobile + .level-left + .level-right + .level-item(style = "color: " + chart.colorInvert) #{chart.title} + .level-item.is-size-4(style = (level == "ネ申") ? "color: gold; text-shadow: 0 0 5px gold" : "") #{level} + .column + .columns.is-vcentered.is-mobile(style = "margin-bottom: 0") + .column + - + let leftProgress = (s.gaugeTimes10 >= 700) ? "70%" : ((s.gaugeTimes10 / 10) + "%") + let rightProgress = (s.gaugeTimes10 < 700) ? "0" : (((s.gaugeTimes10 - 700) / 10) + "%") + .mulprogress(style = "width: 100%; height: 16px; border-radius: 8px; overflow: hidden; position: relative;") + .leftprogress(style = "float: left; height: 100%; border-radius: 8px 0 0 8px; background-color: #2EBECC; width: " + leftProgress) + .rightprogress(style = "float: left; height: 100%; background-color: #F151F1; width: " + rightProgress) + .forepattern(style = "position: absolute; top: 0; left: 0; bottom: 0; right: 0; background: linear-gradient(315deg, " + progresspattern + ")") + .column.is-narrow + p(style = "width: 60px") #{(s.gaugeTimes10 / 10).toFixed(1)}% + div(style = "margin-bottom: 28px; border-bottom: 3px dashed grey;") + .columns.is-vcentered.is-mobile + .column.is-narrow + p Score + .column.has-text-right + p.is-size-1 #{s.score.toString().padStart(7, "0")} + .box.is-size-7(style = "margin-bottom: 0; padding: 8px; background: black; color: white; box-shadow: 0 3px 4px #FFFFFF9F, 0 0 0 2px #FFFFFF3F;") + .columns.is-vcentered.is-mobile.has-text-centered(style = "margin-bottom: 0") + - let f = (bst2Customization.custom[7] | 0b00000001) == bst2Customization.custom[7] + .column.is-one-quarter + if f + span(style = "color: #FF6FC9") F + span(style = "color: #FFC90A") a + span(style = "color: #E7FF18") n + span(style = "color: #57FD4D") t + span(style = "color: #1BFCD7") a + span(style = "color: #1CD2FF") s + span(style = "color: #7255FB") t + span(style = "color: #A835F9") i + span(style = "color: #D512E8") c + else + span(style = "color: #EF005B") サ + span(style = "color: #37FFC8") イ + span(style = "color: #FFEE5F") コ + span(style = "color: #EF005B") ー + span(style = "color: #37FFC8") ! + p #{(s.fantasticCount != null) ? s.fantasticCount.toString().padStart(4, "0") : "----"} + .column.is-one-quarter + if f + span(style = "color: #33E7AD") Great + else + span(style = "color: #37FFC8") ヨッシャー + p #{(s.greatCount != null) ? s.greatCount.toString().padStart(4, "0") : "----"} + .column.is-one-quarter + if f + span(style = "color: #FFC500") Fine + else + span(style = "color: #FFEE5F") オシイ + p #{(s.fineCount != null) ? s.fineCount.toString().padStart(4, "0") : "----"} + .column.is-one-quarter + if f + span(style = "color: #EF005B") miss + else + span(style = "color: #FF1D48; font-weight: bold; -webkit-text-stroke: 0.8px black; text-shadow: 0 0 1px #FF1D48, 0 0 1px #FF1D48, 0 0 1px #FF1D48, 0 0 1px #FF1D48") スカッ + p #{(s.missCount != null) ? s.missCount.toString().padStart(4, "0") : "----"} + .columns.is-vcentered.is-mobile + .column.is-one-quarter + .column.is-half.has-text-right + p Max Combo + .column.is-one-quarter.has-text-centered + p #{s.combo.toString().padStart(4, "0")} + .columns.is-vcentered.is-mobile + .column + .column.is-half.has-text-right(style = "position: relative") + div.has-text-centered(style = "position: absolute; transform: rotate(20deg) scale(0.7); top: 28px; right: -8px; color: " + clearType.color) + span(style = "position: relative; z-index: 1; background: white; padding: 0 8px; clip-path: polygon(0 10%, 100% 10%, 90% 47%, 100% 85%, 0 85%, 10% 47%);") #{clearType.title} + i(class = clearType.badge, style = "position: absolute; top: -22px; left: 2px; right: 2px; z-index: 0;") + p(style = "font-size: 3.8rem; color: " + rank.colorInvert) #{rank.title} + td(style = "color: " + chart.color) #{chart.title} + td(style = (level == "ネ申") ? "color: gold; text-shadow: 0 0 5px gold" : ("color: " + chart.color)) #{level} + td + abbr(title = clearType.title) #{clearType.abbr} + td #{s.score} + td #{s.stageId + 1} + td #{new Date(s.time).toLocaleString()} + .hidden-wrapper + .pagination + a.pagination-previous + i.mdi.mdi-chevron-left + | Newer + a.pagination-next + | Older + i.mdi.mdi-chevron-right + ul.pagination-list#tabs + .card + .card-header + p.card-header-title + span.icon + i.mdi.mdi-account-edit + | Player Settings + a.card-header-icon.card-toggle(card = "edit-settings") + span.icon + i.mdi.mdi-chevron-down + .card-content(card = "edit-settings") + #tab-content(tab-group = "version", tab-index = "2", class = (defaultVersion == 2) ? "is-active" : "") + if bst2Account == null + .has-text-grey.has-text-centered + p + span.icon.is-large + i.mdi.mdi-emoticon-happy.mdi-48px + p Nothing's here yet. Let's play! + else + form#validatable(method="post" action="/emit/bst2UpdateSettings") + input(type = "hidden", name = "refid", value = refid) + .field.is-horizontal#validatable + .field-label.is-normal + label.label Name + .field-body + .control + input.input#validatable(type = "text", name = "name", min-length = "1", max-length = "8", min-pattern = "^[a-zA-Z0-9\.\-_·・&!\?/\*#♯♭★@♪↓↑→←()∞◆●▼¥∀^ ]{1,8}$", value = bst2Base.name) + p.help 1~8 letter(s) and symbol(s). + .field.is-horizontal + .field-label.is-normal + label.label Ripple Note + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "rippleNote", value = bst2Customization.custom[0]) + .select + select#form-select-select + option Default + option Biggger + option Drop + option Shutter + .field.is-horizontal + .field-label.is-normal + label.label Normal Note Sound + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "sfxNormalNote", value = bst2Customization.custom[2]) + .select + select#form-select-select + option Normal Set A + option Ripple Set A + option Slash Set A + option Stream Set A + option Normal Set B + option Ripple Set B + option Slash Set B + option Stream Set B + option Normal Set C + option Ripple Set C + option Slash Set C + option Stream Set C + .field.is-horizontal + .field-label.is-normal + label.label Ripple Note Sound + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "sfxRippleNote", value = bst2Customization.custom[3]) + .select + select#form-select-select + option Normal Set A + option Ripple Set A + option Slash Set A + option Stream Set A + option Normal Set B + option Ripple Set B + option Slash Set B + option Stream Set B + option Normal Set C + option Ripple Set C + option Slash Set C + option Stream Set C + .field.is-horizontal + .field-label.is-normal + label.label Slash Note Sound + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "sfxSlashNote", value = bst2Customization.custom[4]) + .select + select#form-select-select + option Normal Set A + option Ripple Set A + option Slash Set A + option Stream Set A + option Normal Set B + option Ripple Set B + option Slash Set B + option Stream Set B + option Normal Set C + option Ripple Set C + option Slash Set C + option Stream Set C + .field.is-horizontal + .field-label.is-normal + label.label Stream Note Sound + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "sfxStreamNote", value = bst2Customization.custom[5]) + .select + select#form-select-select + option Normal Set A + option Ripple Set A + option Slash Set A + option Stream Set A + option Normal Set B + option Ripple Set B + option Slash Set B + option Stream Set B + option Normal Set C + option Ripple Set C + option Slash Set C + option Stream Set C + .field.is-horizontal + .field-label.is-normal + label.label Background Brightness + .field-body + .field.has-addons#form-numeric + .control + button.button.is-light#form-numeric-sub(type = "button") + + .control + input.input#form-numeric-input(name = "backgroundBrightness", type = "hidden", min-value = "0", max-value = "200", step = "1", value = bst2Customization.custom[6] readonly) + input.input#form-numeric-input(style = "width: 72px; text-align: center", min-value = "100.0", max-value = "0.0", step = "-0.5", digit-count = "1", value = 100 - bst2Customization.custom[6] * 0.5, readonly) + .control + button.button.is-light#form-numeric-add(type = "button") - + .field.is-horizontal + .field-label.is-normal + label.label Judge Text + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "judgeText", value = ((bst2Customization.custom[7] | 0b00000001) == bst2Customization.custom[7]) ? 1 : 0) + .select + select#form-select-select + option Bst2 // サイコー! + option Bst1 // Fantastic + .field.is-horizontal + .field-label.is-normal + label.label Ripple Note Prompt + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "rippleNoteGuide", value = ((bst2Customization.custom[7] | 0b00000010) == bst2Customization.custom[7]) ? 1 : 0) + .select + select#form-select-select + option On + option Off + .field.is-horizontal + .field-label.is-normal + label.label Stream Note Prompt + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "streamNoteGuide", value = ((bst2Customization.custom[7] | 0b00000100) == bst2Customization.custom[7]) ? 1 : 0) + .select + select#form-select-select + option On + option Off + .field.is-horizontal + .field-label.is-normal + label.label Stream Note Tail SFX + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "sfxStreamNoteTail", value = ((bst2Customization.custom[7] | 0b00001000) == bst2Customization.custom[7]) ? 1 : 0) + .select + select#form-select-select + option On + option Off + .field.is-horizontal + .field-label.is-normal + label.label Inaccurate SFX + .field-body + .control#form-select + input#form-select-input(type = "hidden", name = "sfxFine", value = ((bst2Customization.custom[7] | 0b00010000) == bst2Customization.custom[7]) ? 1 : 0) + .select + select#form-select-select + option On + option Off + .field.is-horizontal + .field-label + .field-body + button.button.is-primary#validatable(type = "submit") + span.icon + i.mdi.mdi-check + span Submit \ No newline at end of file