Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| universaltech:start [2026/06/22 06:13] – jumpstartadmin | universaltech:start [2026/06/22 06:57] (current) – jumpstartadmin | ||
|---|---|---|---|
| Line 6: | Line 6: | ||
| <meta charset=" | <meta charset=" | ||
| < | < | ||
| - | /* ===== UNIVERSAL TECH PAGE ===== */ | + | body.mode_show # |
| - | #dokuwiki__site | + | * { box-sizing: border-box; margin: 0; padding: 0; } |
| + | .navbar.navbar-default { z-index: 10000 !important; } | ||
| + | body: | ||
| #ut-page { | #ut-page { | ||
| position: fixed; | position: fixed; | ||
| - | | + | |
| - | z-index: | + | left: 0; right: 0; bottom: 0; |
| + | z-index: | ||
| background: #0A0816; | background: #0A0816; | ||
| display: flex; | display: flex; | ||
| overflow: hidden; | overflow: hidden; | ||
| font-family: | font-family: | ||
| + | color: #EDE8F5; | ||
| } | } | ||
| - | /* ===== VIDEO BACKGROUND ===== */ | + | /* VIDEO BG */ |
| # | # | ||
| position: absolute; | position: absolute; | ||
| Line 26: | Line 30: | ||
| } | } | ||
| # | # | ||
| - | width: 100%; | + | width: 100%; height: 100%; |
| - | | + | |
| object-fit: cover; | object-fit: cover; | ||
| - | opacity: 0.35; | + | opacity: 0.55; |
| } | } | ||
| # | # | ||
| Line 35: | Line 38: | ||
| position: absolute; | position: absolute; | ||
| inset: 0; | inset: 0; | ||
| - | background: linear-gradient(to right, rgba(10, | + | background: linear-gradient(to right, rgba(10, |
| } | } | ||
| - | /* ===== SIDEBAR ===== */ | + | /* LEFT */ |
| - | #ut-sidebar | + | #ut-left { |
| position: relative; | position: relative; | ||
| z-index: 10; | z-index: 10; | ||
| - | width: 340px; | ||
| - | flex-shrink: | ||
| - | display: flex; | ||
| - | flex-direction: | ||
| - | padding: 28px 0 28px 28px; | ||
| - | gap: 0; | ||
| - | overflow: hidden; | ||
| - | } | ||
| - | |||
| - | # | ||
| - | padding: 0 20px 20px 0; | ||
| - | flex-shrink: | ||
| - | } | ||
| - | |||
| - | #ut-logo { | ||
| - | font-size: 11px; | ||
| - | letter-spacing: | ||
| - | text-transform: | ||
| - | color: #6B4FBB; | ||
| - | margin-bottom: | ||
| - | } | ||
| - | |||
| - | # | ||
| - | font-size: 26px; | ||
| - | color: #EDE8F5; | ||
| - | font-weight: | ||
| - | letter-spacing: | ||
| - | line-height: | ||
| - | margin-bottom: | ||
| - | } | ||
| - | |||
| - | #ut-search { | ||
| - | width: 100%; | ||
| - | background: rgba(255, | ||
| - | border: 1px solid rgba(108, | ||
| - | border-radius: | ||
| - | padding: 9px 14px; | ||
| - | color: #EDE8F5; | ||
| - | font-size: 13px; | ||
| - | font-family: | ||
| - | outline: none; | ||
| - | transition: border-color 0.15s; | ||
| - | } | ||
| - | # | ||
| - | # | ||
| - | |||
| - | /* ===== TILE LIST ===== */ | ||
| - | # | ||
| flex: 1; | flex: 1; | ||
| - | overflow-y: auto; | ||
| - | overflow-x: hidden; | ||
| - | padding-right: | ||
| display: flex; | display: flex; | ||
| flex-direction: | flex-direction: | ||
| - | | + | |
| - | padding-bottom: 20px; | + | padding: 0 0 64px 64px; |
| + | pointer-events: none; | ||
| } | } | ||
| - | # | ||
| - | # | ||
| - | # | ||
| - | /* ===== CATEGORY TILE ===== */ | + | #ut-splash, #ut-content |
| - | .ut-cat-wrap { | + | transition: |
| - | flex-shrink: | + | |
| - | transition: | + | |
| } | } | ||
| - | + | #ut-splash.hidden, #ut-content.hidden { | |
| - | .ut-cat-tile { | + | |
| - | position: relative; | + | |
| - | border-radius: | + | |
| - | overflow: hidden; | + | |
| - | cursor: pointer; | + | |
| - | transition: transform 0.25s cubic-bezier(0.4, | + | |
| - | height: 72px; | + | |
| - | border: 1px solid rgba(108, | + | |
| - | user-select: | + | |
| - | } | + | |
| - | + | ||
| - | .ut-cat-tile.active { | + | |
| - | border-color: | + | |
| - | height: 72px; | + | |
| - | } | + | |
| - | + | ||
| - | .ut-cat-tile.sibling-shrink { | + | |
| - | height: 52px; | + | |
| - | opacity: 0.7; | + | |
| - | } | + | |
| - | + | ||
| - | .ut-cat-tile-bg { | + | |
| - | position: absolute; | + | |
| - | inset: 0; | + | |
| - | opacity: 0.18; | + | |
| - | transition: opacity 0.2s; | + | |
| - | } | + | |
| - | .ut-cat-tile: | + | |
| - | .ut-cat-tile.active .ut-cat-tile-bg { opacity: 0.32; } | + | |
| - | + | ||
| - | .ut-cat-tile-gradient { | + | |
| - | position: absolute; | + | |
| - | inset: 0; | + | |
| - | background: linear-gradient(105deg, | + | |
| - | } | + | |
| - | .ut-cat-tile.active .ut-cat-tile-gradient { | + | |
| - | background: linear-gradient(105deg, | + | |
| - | } | + | |
| - | + | ||
| - | .ut-cat-tile-content { | + | |
| - | position: relative; | + | |
| - | z-index: 2; | + | |
| - | display: flex; | + | |
| - | align-items: | + | |
| - | gap: 14px; | + | |
| - | height: 100%; | + | |
| - | padding: 0 18px; | + | |
| - | transition: padding 0.25s; | + | |
| - | } | + | |
| - | + | ||
| - | .ut-cat-icon { | + | |
| - | font-size: 22px; | + | |
| - | color: | + | |
| - | flex-shrink: | + | |
| - | transition: font-size 0.25s, color 0.2s; | + | |
| - | } | + | |
| - | .ut-cat-tile.active .ut-cat-icon { color: #C8BFED; font-size: 24px; } | + | |
| - | .ut-cat-tile.sibling-shrink .ut-cat-icon { font-size: 17px; } | + | |
| - | + | ||
| - | .ut-cat-text { flex: 1; min-width: 0; } | + | |
| - | .ut-cat-label { | + | |
| - | font-size: 15px; | + | |
| - | font-weight: | + | |
| - | color: #C8BFED; | + | |
| - | letter-spacing: | + | |
| - | transition: font-size 0.25s, color 0.2s; | + | |
| - | white-space: | + | |
| - | overflow: hidden; | + | |
| - | text-overflow: | + | |
| - | } | + | |
| - | .ut-cat-tile.active .ut-cat-label { color: | + | |
| - | .ut-cat-tile.sibling-shrink .ut-cat-label { font-size: 13px; } | + | |
| - | + | ||
| - | .ut-cat-count { | + | |
| - | font-size: 11px; | + | |
| - | color: #6B4FBB; | + | |
| - | margin-top: 2px; | + | |
| - | transition: opacity 0.2s; | + | |
| - | } | + | |
| - | .ut-cat-tile.sibling-shrink .ut-cat-count { opacity: 0; } | + | |
| - | + | ||
| - | .ut-cat-chevron { | + | |
| - | font-size: 14px; | + | |
| - | color: #6B4FBB; | + | |
| - | transition: transform 0.25s, color 0.2s; | + | |
| - | flex-shrink: | + | |
| - | } | + | |
| - | .ut-cat-tile.active .ut-cat-chevron { transform: rotate(90deg); | + | |
| - | + | ||
| - | /* ===== SUB-TILES ===== */ | + | |
| - | .ut-sub-list { | + | |
| - | display: flex; | + | |
| - | flex-direction: | + | |
| - | gap: 5px; | + | |
| - | overflow: | + | |
| - | max-height: 0; | + | |
| - | transition: max-height 0.35s cubic-bezier(0.4, | + | |
| - | margin-top: 0; | + | |
| - | } | + | |
| - | .ut-sub-list.open | + | |
| - | max-height: 600px; | + | |
| - | margin-top: 6px; | + | |
| - | } | + | |
| - | + | ||
| - | .ut-sub-tile { | + | |
| - | border-radius: | + | |
| - | height: 46px; | + | |
| - | display: flex; | + | |
| - | align-items: | + | |
| - | gap: 12px; | + | |
| - | padding: 0 16px 0 24px; | + | |
| - | cursor: pointer; | + | |
| - | background: rgba(61, | + | |
| - | border: 1px solid rgba(108, | + | |
| - | transition: background 0.15s, border-color 0.15s, transform 0.15s; | + | |
| - | transform: translateX(-20px); | + | |
| opacity: 0; | opacity: 0; | ||
| - | | + | transform: |
| - | } | + | |
| - | .ut-sub-list.open .ut-sub-tile { | + | |
| - | animation: subSlideIn 0.3s cubic-bezier(0.4, | + | |
| - | } | + | |
| - | .ut-sub-list.open .ut-sub-tile: | + | |
| - | .ut-sub-list.open .ut-sub-tile: | + | |
| - | .ut-sub-list.open .ut-sub-tile: | + | |
| - | .ut-sub-list.open .ut-sub-tile: | + | |
| - | .ut-sub-list.open .ut-sub-tile: | + | |
| - | .ut-sub-list.open .ut-sub-tile: | + | |
| - | @keyframes subSlideIn { | + | |
| - | to { transform: | + | |
| - | } | + | |
| - | + | ||
| - | .ut-sub-tile: | + | |
| - | background: rgba(108, | + | |
| - | border-color: | + | |
| - | transform: translateX(3px) !important; | + | |
| - | } | + | |
| - | .ut-sub-tile.active { | + | |
| - | background: rgba(108, | + | |
| - | border-color: | + | |
| - | transform: translateX(3px) !important; | + | |
| - | } | + | |
| - | + | ||
| - | .ut-sub-dot { | + | |
| - | width: 6px; | + | |
| - | height: 6px; | + | |
| - | border-radius: | + | |
| - | background: #6B4FBB; | + | |
| - | flex-shrink: | + | |
| - | transition: background 0.15s; | + | |
| - | } | + | |
| - | .ut-sub-tile.active .ut-sub-dot, | + | |
| - | .ut-sub-tile: | + | |
| - | + | ||
| - | .ut-sub-label { | + | |
| - | font-size: 13px; | + | |
| - | color: #9D85DB; | + | |
| - | flex: 1; | + | |
| - | transition: color 0.15s; | + | |
| - | } | + | |
| - | .ut-sub-tile.active .ut-sub-label, | + | |
| - | .ut-sub-tile: | + | |
| - | + | ||
| - | .ut-sub-arrow { | + | |
| - | font-size: 12px; | + | |
| - | color: #6B4FBB; | + | |
| - | opacity: 0; | + | |
| - | transition: opacity 0.15s; | + | |
| - | } | + | |
| - | .ut-sub-tile: | + | |
| - | .ut-sub-tile.active .ut-sub-arrow { opacity: 1; } | + | |
| - | + | ||
| - | /* ===== MAIN AREA ===== */ | + | |
| - | #ut-main { | + | |
| - | position: relative; | + | |
| - | z-index: 5; | + | |
| - | flex: 1; | + | |
| - | display: flex; | + | |
| - | flex-direction: | + | |
| - | align-items: | + | |
| - | justify-content: | + | |
| - | padding: 0 60px 60px 40px; | + | |
| pointer-events: | pointer-events: | ||
| + | position: absolute; | ||
| } | } | ||
| + | #ut-content { pointer-events: | ||
| - | /* splash state */ | + | .ut-eyebrow |
| - | #ut-splash { | + | |
| - | transition: opacity 0.3s, transform 0.3s; | + | |
| - | } | + | |
| - | #ut-splash.hidden { opacity: 0; transform: translateY(10px); | + | |
| - | + | ||
| - | # | + | |
| font-size: 11px; | font-size: 11px; | ||
| - | letter-spacing: | + | letter-spacing: |
| text-transform: | text-transform: | ||
| color: #6B4FBB; | color: #6B4FBB; | ||
| Line 308: | Line 72: | ||
| } | } | ||
| # | # | ||
| - | font-size: | + | font-size: |
| font-weight: | font-weight: | ||
| + | line-height: | ||
| + | letter-spacing: | ||
| + | margin-bottom: | ||
| color: #EDE8F5; | color: #EDE8F5; | ||
| - | line-height: | ||
| - | letter-spacing: | ||
| - | margin-bottom: | ||
| } | } | ||
| # | # | ||
| Line 319: | Line 83: | ||
| font-size: 15px; | font-size: 15px; | ||
| color: #9D85DB; | color: #9D85DB; | ||
| - | max-width: | + | max-width: |
| - | line-height: | + | line-height: |
| - | margin-bottom: | + | |
| - | } | + | |
| - | # | + | |
| - | display: flex; | + | |
| - | flex-wrap: wrap; | + | |
| - | gap: 8px; | + | |
| - | pointer-events: | + | |
| - | } | + | |
| - | .ut-splash-pill { | + | |
| - | background: rgba(61, | + | |
| - | border: 1px solid rgba(108, | + | |
| - | border-radius: | + | |
| - | padding: 6px 16px; | + | |
| - | font-size: 12px; | + | |
| - | color: #B8ADDB; | + | |
| - | cursor: pointer; | + | |
| - | transition: all 0.15s; | + | |
| - | font-family: | + | |
| - | } | + | |
| - | .ut-splash-pill: | + | |
| - | background: rgba(108, | + | |
| - | color: #EDE8F5; | + | |
| - | border-color: | + | |
| } | } | ||
| /* content state */ | /* content state */ | ||
| - | #ut-content { | ||
| - | position: absolute; | ||
| - | bottom: 60px; | ||
| - | left: 40px; | ||
| - | right: 60px; | ||
| - | transition: opacity 0.3s, transform 0.3s; | ||
| - | pointer-events: | ||
| - | } | ||
| - | # | ||
| - | |||
| # | # | ||
| font-size: 11px; | font-size: 11px; | ||
| - | letter-spacing: | + | letter-spacing: |
| text-transform: | text-transform: | ||
| color: #6B4FBB; | color: #6B4FBB; | ||
| Line 365: | Line 96: | ||
| } | } | ||
| # | # | ||
| - | font-size: | + | font-size: |
| font-weight: | font-weight: | ||
| - | | + | letter-spacing: |
| - | | + | |
| line-height: | line-height: | ||
| - | margin-bottom: | + | margin-bottom: |
| + | color: #EDE8F5; | ||
| } | } | ||
| # | # | ||
| font-size: 14px; | font-size: 14px; | ||
| color: #9D85DB; | color: #9D85DB; | ||
| - | max-width: | + | max-width: |
| - | line-height: | + | line-height: |
| margin-bottom: | margin-bottom: | ||
| } | } | ||
| Line 382: | Line 113: | ||
| display: flex; | display: flex; | ||
| flex-wrap: wrap; | flex-wrap: wrap; | ||
| - | gap: 10px; | + | gap: 8px; |
| - | margin-bottom: | + | margin-bottom: |
| + | pointer-events: | ||
| } | } | ||
| .ut-guide-card { | .ut-guide-card { | ||
| - | background: rgba(26,20,51,0.65); | + | background: rgba(18,15,41,0.7); |
| - | border: 1px solid rgba(108, | + | border: 1px solid rgba(108, |
| - | border-radius: | + | border-radius: |
| - | padding: | + | padding: |
| cursor: pointer; | cursor: pointer; | ||
| transition: all 0.15s; | transition: all 0.15s; | ||
| - | backdrop-filter: | ||
| text-decoration: | text-decoration: | ||
| display: block; | display: block; | ||
| - | min-width: 160px; | ||
| } | } | ||
| .ut-guide-card: | .ut-guide-card: | ||
| - | background: rgba(61, | + | background: rgba(61, |
| - | border-color: | + | border-color: |
| transform: translateY(-2px); | transform: translateY(-2px); | ||
| } | } | ||
| - | .ut-guide-card-name { | + | .ut-guide-card-name { font-size: 13px; font-weight: |
| - | | + | .ut-guide-card-meta { font-size: 11px; color: #6B4FBB; } |
| - | | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | .ut-guide-card-meta { | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| # | # | ||
| Line 417: | Line 139: | ||
| align-items: | align-items: | ||
| gap: 8px; | gap: 8px; | ||
| - | background: rgba(108, | + | background: rgba(108, |
| - | border: 1px solid rgba(108, | + | border: 1px solid rgba(108, |
| border-radius: | border-radius: | ||
| padding: 10px 20px; | padding: 10px 20px; | ||
| Line 425: | Line 147: | ||
| font-family: | font-family: | ||
| cursor: pointer; | cursor: pointer; | ||
| - | transition: all 0.15s; | ||
| text-decoration: | text-decoration: | ||
| + | transition: all 0.15s; | ||
| + | pointer-events: | ||
| } | } | ||
| # | # | ||
| - | background: rgba(108, | + | background: rgba(108, |
| color: #EDE8F5; | color: #EDE8F5; | ||
| border-color: | border-color: | ||
| } | } | ||
| - | /* ===== CATEGORY TILE ACCENT COLORS ===== */ | + | /* RIGHT — osu carousel |
| - | .ut-accent-movement .ut-cat-tile-bg { background: linear-gradient(135deg, | + | #ut-carousel-wrap { |
| - | .ut-accent-combat .ut-cat-tile-bg { background: linear-gradient(135deg, | + | position: relative; |
| - | .ut-accent-items .ut-cat-tile-bg { background: linear-gradient(135deg, | + | z-index: 10; |
| - | .ut-accent-map .ut-cat-tile-bg { background: linear-gradient(135deg, | + | width: 500px; |
| - | .ut-accent-mechanics .ut-cat-tile-bg { background: linear-gradient(135deg, | + | flex-shrink: 0; |
| - | .ut-accent-abilities .ut-cat-tile-bg { background: linear-gradient(135deg, | + | |
| + | display: flex; | ||
| + | align-items: center; | ||
| + | user-select: none; | ||
| + | } | ||
| + | # | ||
| + | position: absolute; | ||
| + | right: 0; | ||
| + | width: 100%; | ||
| + | } | ||
| + | |||
| + | /* tiles */ | ||
| + | .ut-item { | ||
| + | position: absolute; | ||
| + | right: 0; | ||
| + | border-radius: | ||
| + | overflow: hidden; | ||
| + | cursor: pointer; | ||
| + | border: 1px solid rgba(108, | ||
| + | border-right: | ||
| + | will-change: | ||
| + | transition: | ||
| + | width 0.22s cubic-bezier(0.4, | ||
| + | height 0.22s cubic-bezier(0.4, | ||
| + | opacity 0.22s; | ||
| + | } | ||
| + | |||
| + | .ut-item.is-cat | ||
| + | .ut-item.is-sub | ||
| + | .ut-item.is-cat.selected { width: 480px; height: 96px; } | ||
| + | .ut-item.is-sub.selected { width: 440px; height: 72px; } | ||
| + | |||
| + | .ut-item-bg { | ||
| + | position: absolute; inset: 0; | ||
| + | opacity: 0.2; | ||
| + | transition: opacity 0.2s; | ||
| + | } | ||
| + | .ut-item: | ||
| + | .ut-item.selected .ut-item-bg { opacity: 0.38; } | ||
| + | |||
| + | .ut-item-overlay { | ||
| + | position: absolute; inset: 0; | ||
| + | background: linear-gradient(90deg, | ||
| + | } | ||
| + | |||
| + | .ut-item-inner { | ||
| + | position: relative; | ||
| + | z-index: 2; | ||
| + | display: flex; | ||
| + | align-items: | ||
| + | gap: 16px; | ||
| + | padding: 0 22px; | ||
| + | width: 100%; | ||
| + | height: 100%; | ||
| + | } | ||
| + | |||
| + | .ut-item-icon { | ||
| + | font-size: 22px; | ||
| + | color: #9D85DB; | ||
| + | flex-shrink: | ||
| + | transition: color 0.2s, font-size 0.2s; | ||
| + | } | ||
| + | .ut-item.selected .ut-item-icon | ||
| + | .ut-item.is-sub .ut-item-icon | ||
| + | .ut-item.is-sub.selected .ut-item-icon { font-size: 17px; color: #9D85DB; } | ||
| + | |||
| + | .ut-item-text { flex: 1; min-width: 0; } | ||
| + | .ut-item-label { | ||
| + | font-size: 15px; | ||
| + | font-weight: | ||
| + | color: #C8BFED; | ||
| + | white-space: | ||
| + | overflow: hidden; | ||
| + | text-overflow: | ||
| + | transition: color 0.2s, font-size 0.2s; | ||
| + | } | ||
| + | .ut-item.selected .ut-item-label | ||
| + | .ut-item.is-sub .ut-item-label | ||
| + | .ut-item.is-sub.selected .ut-item-label { color: #C8BFED; font-size: 14px; } | ||
| + | |||
| + | .ut-item-sub-count { | ||
| + | font-size: 11px; | ||
| + | color: #6B4FBB; | ||
| + | margin-top: 3px; | ||
| + | } | ||
| + | |||
| + | .ut-item-accent-bar { | ||
| + | position: absolute; | ||
| + | left: 0; top: 0; bottom: 0; | ||
| + | width: 3px; | ||
| + | border-radius: | ||
| + | opacity: 0; | ||
| + | transition: opacity 0.2s; | ||
| + | } | ||
| + | .ut-item.selected .ut-item-accent-bar { opacity: 1; } | ||
| + | |||
| + | /* accent colors */ | ||
| + | .ut-acc-movement .ut-item-bg | ||
| + | .ut-acc-movement .ut-item-accent-bar { background: #9D85DB; } | ||
| + | .ut-acc-combat .ut-item-bg | ||
| + | .ut-acc-combat .ut-item-accent-bar | ||
| + | .ut-acc-items .ut-item-bg | ||
| + | .ut-acc-items .ut-item-accent-bar | ||
| + | .ut-acc-map .ut-item-bg | ||
| + | .ut-acc-map .ut-item-accent-bar | ||
| + | .ut-acc-mechanics .ut-item-bg { background: linear-gradient(90deg,# | ||
| + | .ut-acc-mechanics .ut-item-accent-bar{ background: #DBC085; } | ||
| + | .ut-acc-abilities .ut-item-bg { background: linear-gradient(90deg,# | ||
| + | .ut-acc-abilities .ut-item-accent-bar{ background: #C085DB; } | ||
| + | |||
| + | /* search */ | ||
| + | # | ||
| + | position: absolute; | ||
| + | top: 24px; right: 0; | ||
| + | width: 440px; | ||
| + | z-index: 20; | ||
| + | } | ||
| + | #ut-search { | ||
| + | width: 100%; | ||
| + | background: rgba(10, | ||
| + | border: 1px solid rgba(108, | ||
| + | border-right: | ||
| + | border-radius: | ||
| + | padding: 10px 16px; | ||
| + | color: #EDE8F5; | ||
| + | font-size: 13px; | ||
| + | font-family: | ||
| + | outline: none; | ||
| + | transition: border-color 0.15s; | ||
| + | } | ||
| + | # | ||
| + | # | ||
| </ | </ | ||
| </ | </ | ||
| < | < | ||
| - | |||
| <div id=" | <div id=" | ||
| - | <!-- VIDEO BACKGROUND --> | ||
| <div id=" | <div id=" | ||
| <video autoplay muted loop playsinline id=" | <video autoplay muted loop playsinline id=" | ||
| - | <source src="/ | + | <source src="/ |
| - | <source src="/ | + | |
| </ | </ | ||
| </ | </ | ||
| - | | + | <div id=" |
| - | <div id=" | + | |
| - | <div id=" | + | |
| - | <div id=" | + | |
| - | < | + | |
| - | <input type=" | + | |
| - | </ | + | |
| - | <div id=" | + | |
| - | </ | + | |
| - | + | ||
| - | <!-- MAIN AREA --> | + | |
| - | | + | |
| <div id=" | <div id=" | ||
| - | < | + | < |
| <div id=" | <div id=" | ||
| - | <div id=" | + | <div id=" |
| - | <div id=" | + | |
| </ | </ | ||
| - | |||
| <div id=" | <div id=" | ||
| <div id=" | <div id=" | ||
| Line 481: | Line 318: | ||
| <div id=" | <div id=" | ||
| <div id=" | <div id=" | ||
| - | <a id=" | + | <a id=" |
| - | | + | |
| - | | + | |
| - | | + | |
| </ | </ | ||
| + | </ | ||
| + | <div id=" | ||
| + | <div id=" | ||
| + | <input type=" | ||
| + | </ | ||
| + | <div id=" | ||
| </ | </ | ||
| - | </ | ||
| + | </ | ||
| < | < | ||
| (function(){ | (function(){ | ||
| const CATS = [ | const CATS = [ | ||
| - | { | + | { id:' |
| - | | + | desc:' |
| - | desc: ' | + | wikiId:' |
| - | wikiId: ' | + | subs:[ |
| - | subs: [ | + | {id:'walking', |
| - | { id: 'bunnyhop', label: 'Bunny Hop', desc: 'Speed maintenance between jumps.', wikiId: ' | + | {id:'sprinting', label:' |
| - | { id: 'airstrafing', label: 'Air Strafing', desc: 'Directional control while airborne.', wikiId: ' | + | {id:'crouching', label:' |
| - | { id: 'walljump', label: 'Wall Jump', desc: 'Bouncing off geometry for height or distance.', wikiId: ' | + | {id:'sliding', |
| - | { id: 'slidehop', label: ' | + | {id:' |
| - | { id: 'zipline', label: 'Zipline Tech', desc: 'Advanced entry, exit, and speed tricks.', wikiId: ' | + | {id:'jumping', |
| - | ] | + | ]}, |
| - | }, | + | |
| - | { | + | desc:' |
| - | id: 'combat', icon: 'ti-crosshair', label: 'Combat', | + | wikiId:' |
| - | desc: 'Engagement techniques | + | subs:[ |
| - | wikiId: ' | + | {id:' |
| - | subs: [ | + | |
| - | { id: 'peekshoot', label: 'Peek Shooting', desc: 'Minimize exposure while landing shots.', wikiId: ' | + | {id:' |
| - | { id: 'jumpshoot', label: ' | + | |
| - | { id: 'slideshoot', label: 'Slide Shooting', desc: 'Aim control through slide animations.', wikiId: ' | + | {id:' |
| - | { id: 'reloadcancel', label: 'Reload Cancel', desc: 'Safely cutting reload animations short.', wikiId: ' | + | |
| - | ] | + | { id:'mantling', icon:' |
| - | }, | + | desc:'Mantle techs — advanced ledge grabs, super glides, and mantle-based launches.', |
| - | { | + | wikiId:' |
| - | id: 'items', icon: 'ti-package', label: 'Item Tech', | + | subs:[ |
| - | desc: 'Hidden | + | {id:'mantleslide', |
| - | wikiId: ' | + | {id:'superglide', |
| - | subs: [ | + | {id:' |
| - | { id: 'warpstone', label: 'Warp Stone', desc: 'Positioning tricks | + | {id:' |
| - | { id: 'cloakmantle', label: 'Cloak + Mantle', desc: 'Invisibility combined with mobility items.', wikiId: ' | + | {id:' |
| - | { id: 'coldfront', label: 'Cold Front', desc: 'Slow tech and zone control setups.', wikiId: ' | + | {id:' |
| - | { id: 'magiccarpet', label: 'Magic Carpet', desc: 'Hover positioning | + | ]}, |
| - | ] | + | { id:' |
| - | | + | desc:' |
| - | { | + | wikiId:' |
| - | | + | subs:[ |
| - | desc: 'Geometry exploits, map-specific routes, and positional advantages.', | + | {id:' |
| - | wikiId: ' | + | {id:'zipcancel', |
| - | subs: [ | + | {id:'zipjump', label:' |
| - | { id: 'roofjumps', label: 'Roof Jumps', desc: 'Common roof access points across all lanes.', wikiId: ' | + | {id:' |
| - | { id: 'shortcuts', label: 'Shortcuts', desc: 'Fast-path routes used in high-level play.', wikiId: ' | + | {id:' |
| - | { id: 'oob', label: 'Out-of-Bounds Tech', desc: 'Legal geometry | + | ]}, |
| - | ] | + | |
| - | }, | + | desc:' |
| - | { | + | wikiId:' |
| - | id: 'mechanics', icon: 'ti-settings', label: 'Game Mechanics', | + | subs:[ |
| - | desc: 'Engine | + | {id:' |
| - | wikiId: ' | + | {id:' |
| - | subs: [ | + | {id:' |
| - | { id: 'animation', label: 'Animation Cancels', desc: 'Breaking animations to act faster.', wikiId: ' | + | |
| - | { id: 'momentum', label: 'Momentum Preservation', desc: 'How the engine stores | + | { id:'vent', icon:' |
| - | { id: 'phasing', label: 'Phasing Windows', desc: 'Invincibility | + | desc:'Vent interactions — boost techs and stun mechanics.', |
| - | ] | + | wikiId:' |
| - | | + | subs:[ |
| - | { | + | {id:' |
| - | | + | {id:' |
| - | desc: ' | + | ]}, |
| - | wikiId: ' | + | { id:' |
| - | subs: [ | + | desc:' |
| - | { id: 'quickcast', label: 'Quick Cast', desc: 'Instant ability activation setups.', wikiId: ' | + | wikiId:' |
| - | { id: 'buffstack', label: 'Buff Stacking', desc: 'Layering buffs for maximum burst.', wikiId: ' | + | subs:[ |
| - | { id: 'canceltech', label: 'Cancel Tech', desc: 'Cancelling abilities for free casts.', wikiId: ' | + | {id:'lightmelee', label:' |
| - | | + | {id:'heavymelee', label:' |
| - | }, | + | {id:'parry', |
| + | {id:'hmc', | ||
| + | {id:' | ||
| + | {id:' | ||
| + | ]}, | ||
| + | { id:'walljump', icon:' | ||
| + | desc:'Wall jump techs — directional control, corner and edge boosts, surf, and fatigue management.', | ||
| + | wikiId:' | ||
| + | subs:[ | ||
| + | {id:'wjd', | ||
| + | {id:'cornerboost', | ||
| + | {id:'edgeboost', | ||
| + | | ||
| + | | ||
| + | {id:' | ||
| + | | ||
| + | { id:'strafing', icon:' | ||
| + | desc:'Advanced strafe techs — air strafing, bunnyhopping, | ||
| + | wikiId:' | ||
| + | subs:[ | ||
| + | {id:'airstrafe', | ||
| + | {id:'bunnyhop', | ||
| + | {id:'slidebhop', | ||
| + | ]}, | ||
| + | { id:'itemtech', icon:' | ||
| + | desc:' | ||
| + | wikiId:' | ||
| + | subs:[ | ||
| + | {id:'majesticleap', | ||
| + | {id:'warpstone', | ||
| + | {id:'rescuebeam', | ||
| + | | ||
| + | {id:' | ||
| + | | ||
| + | {id:' | ||
| + | ]}, | ||
| ]; | ]; | ||
| + | const VIDS = [ | ||
| + | '/ | ||
| + | '/ | ||
| + | '/ | ||
| + | '/ | ||
| + | ]; | ||
| + | let vi = 0; | ||
| + | const video = document.getElementById(' | ||
| + | video.addEventListener(' | ||
| + | vi = (vi + 1) % VIDS.length; | ||
| + | video.src = VIDS[vi]; | ||
| + | video.play(); | ||
| + | }); | ||
| + | |||
| + | // ── STATE ────────────────────────────────────────────── | ||
| let activeCatId = null; | let activeCatId = null; | ||
| let activeSubId = null; | let activeSubId = null; | ||
| + | let filter = ''; | ||
| - | const tileList | + | // ── LAYOUT CONSTANTS ──────────────────────────────────── |
| - | const searchInput | + | const CAT_H = 80; |
| - | const splash | + | const CAT_H_SEL |
| - | const content | + | const SUB_H = 60; |
| - | const splashPills | + | const SUB_H_SEL |
| + | const GAP = 7; | ||
| - | // Build splash pills | + | // scrollY |
| - | CATS.forEach(cat | + | let scrollY |
| - | const pill = document.createElement(' | + | let velocity |
| - | pill.className | + | let rafId = null; |
| - | | + | let isDragging = false; |
| - | | + | let lastDragY |
| - | | + | let lastDragT |
| - | }); | + | |
| - | + | ||
| - | function buildTiles(filter) { | + | |
| - | tileList.innerHTML | + | |
| - | | + | |
| + | // ── FLAT ITEM LIST ────────────────────────────────────── | ||
| + | function buildItemList() { | ||
| + | const items = []; | ||
| + | const q = filter.toLowerCase(); | ||
| CATS.forEach(cat => { | CATS.forEach(cat => { | ||
| - | const matchCat | + | const subMatch |
| - | const matchSubs = cat.subs.filter(s => !q || s.label.toLowerCase().includes(q) | + | |
| - | if (q && | + | |
| + | if (q && | ||
| + | items.push({ type:' | ||
| + | if (activeCatId === cat.id) { | ||
| + | (q ? subMatch : cat.subs).forEach(sub => items.push({ type:' | ||
| + | } | ||
| + | }); | ||
| + | return items; | ||
| + | } | ||
| - | | + | // ── POSITIONS ─────────────────────────────────────────── |
| + | function computePositions(items) { | ||
| + | let y = 0; | ||
| + | return items.map(item => { | ||
| + | | ||
| + | ? activeCatId === item.cat.id | ||
| + | : activeSubId === item.sub.id; | ||
| + | const h = item.type === ' | ||
| + | const pos = { y, h, isSel }; | ||
| + | y += h + GAP; | ||
| + | return pos; | ||
| + | }); | ||
| + | } | ||
| - | | + | // ── RENDER ───────────────────────────────────────────── |
| - | | + | function render() { |
| - | | + | |
| + | const wrapH | ||
| + | const items | ||
| + | const pos | ||
| + | const totalH | ||
| - | | + | |
| - | const tile = document.createElement(' | + | const minScroll |
| - | tile.className = ' | + | const maxScroll |
| - | | + | |
| - | <div class=" | + | |
| - | < | + | |
| - | <div class=" | + | |
| - | <i class=" | + | |
| - | <div class=" | + | |
| - | <div class=" | + | |
| - | <div class=" | + | |
| - | </ | + | |
| - | <i class=" | + | |
| - | </ | + | |
| - | `; | + | |
| - | tile.addEventListener(' | + | |
| - | | + | |
| - | const subList | + | const existingEls |
| - | | + | carousel.querySelectorAll('.ut-item').forEach(el => { existingEls[el.dataset.uid] = el; }); |
| + | const newUids = new Set(); | ||
| + | items.forEach(item | ||
| + | Object.keys(existingEls).forEach(u => { if (!newUids.has(u)) existingEls[u].remove(); | ||
| - | const visibleSubs = q ? matchSubs : cat.subs; | + | |
| - | visibleSubs.forEach(sub => { | + | const vc = wrapH / 2; |
| - | const isActiveSub = activeSubId === sub.id; | + | |
| - | const subTile = document.createElement(' | + | |
| - | subTile.className = ' | + | |
| - | subTile.innerHTML = ` | + | |
| - | <span class=" | + | |
| - | <span class=" | + | |
| - | <i class="ti ti-arrow-right ut-sub-arrow" | + | |
| - | `; | + | |
| - | subTile.addEventListener(' | + | |
| - | subList.appendChild(subTile); | + | |
| - | }); | + | |
| - | wrap.appendChild(tile); | + | items.forEach((item, i) => { |
| - | | + | const u = uid(item); |
| - | | + | |
| + | |||
| + | // position relative to viewport center | ||
| + | const screenY = iy - scrollY; | ||
| + | const itemCenter = screenY + h / 2; | ||
| + | const distFromCenter = itemCenter - vc; | ||
| + | const absD = Math.abs(distFromCenter); | ||
| + | | ||
| + | |||
| + | // osu curve: items further away pushed right | ||
| + | const pushFrac = Math.min(absD / maxD, 1); | ||
| + | // ease it: more push at the edges | ||
| + | const push = Math.pow(pushFrac, | ||
| + | |||
| + | // fade near edges | ||
| + | const fadeFrac = Math.min(absD / (wrapH * 0.44), 1); | ||
| + | const opacity | ||
| + | |||
| + | if (opacity <= 0.01) { | ||
| + | if (existingEls[u]) existingEls[u].style.opacity = ' | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | let el = existingEls[u]; | ||
| + | if (!el) { | ||
| + | el = document.createElement(' | ||
| + | el.dataset.uid = u; | ||
| + | el.innerHTML = buildInner(item); | ||
| + | el.addEventListener(' | ||
| + | carousel.appendChild(el); | ||
| + | } | ||
| + | |||
| + | el.className = ' | ||
| + | (item.type === ' | ||
| + | ' ut-acc-' | ||
| + | (isSel ? ' selected' | ||
| + | |||
| + | el.style.top | ||
| + | el.style.height | ||
| + | el.style.transform = `translateX(${push}px)`; | ||
| + | el.style.opacity = opacity.toFixed(3); | ||
| }); | }); | ||
| + | |||
| + | carousel.style.height = wrapH + ' | ||
| } | } | ||
| - | function | + | function |
| - | if (activeCatId | + | return item.type === ' |
| - | // deselect | + | } |
| - | | + | |
| - | | + | function buildInner(item) { |
| - | | + | if (item.type |
| + | | ||
| + | <div class=" | ||
| + | <div class=" | ||
| + | <div class=" | ||
| + | <i class=" | ||
| + | <div class=" | ||
| + | | ||
| + | | ||
| + | </ | ||
| + | <i class=" | ||
| + | </ | ||
| } else { | } else { | ||
| - | | + | |
| - | | + | <div class=" |
| - | | + | <div class=" |
| + | <div class=" | ||
| + | <i class=" | ||
| + | <div class=" | ||
| + | | ||
| + | </ | ||
| + | <i class=" | ||
| + | </ | ||
| } | } | ||
| - | buildTiles(searchInput.value); | ||
| - | scrollActiveTileIntoView(); | ||
| } | } | ||
| - | function | + | // ── CLICK ────────────────────────────────────────────── |
| - | activeCatId = catId; | + | function |
| - | activeSubId = subId; | + | |
| - | showSub(catId, subId); | + | if (activeCatId === item.cat.id && activeSubId === null) { |
| - | | + | |
| + | | ||
| + | } else { | ||
| + | activeCatId = item.cat.id; | ||
| + | | ||
| + | showCat(item.cat); | ||
| + | } | ||
| + | | ||
| + | activeCatId = item.cat.id; | ||
| + | activeSubId = item.sub.id; | ||
| + | | ||
| + | | ||
| + | scrollToSelected(); | ||
| + | render(); | ||
| } | } | ||
| + | |||
| + | function scrollToSelected() { | ||
| + | const items = buildItemList(); | ||
| + | const pos = computePositions(items); | ||
| + | const wrapH = document.getElementById(' | ||
| + | let selY = null, selH = CAT_H; | ||
| + | items.forEach((item, | ||
| + | const isSel = item.type === ' | ||
| + | ? activeCatId === item.cat.id && activeSubId === null | ||
| + | : activeSubId === item.sub? | ||
| + | if (isSel) { selY = pos[i].y; selH = pos[i].h; } | ||
| + | }); | ||
| + | if (selY !== null) { | ||
| + | animateScroll(selY + selH / 2 - wrapH / 2); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Spring scroll — sets velocity toward target naturally instead of teleporting | ||
| + | function animateScroll(target) { | ||
| + | // Stop any current momentum and spring toward target | ||
| + | momentumRunning = false; | ||
| + | velocity = 0; | ||
| + | const SPRING = 0.12; // attraction strength per frame | ||
| + | const DAMP = 0.72; // damping to prevent overshoot | ||
| + | let springRunning = true; | ||
| + | let sv = 0; // spring velocity separate from main velocity | ||
| + | function step() { | ||
| + | if (!springRunning) return; | ||
| + | const diff = target - scrollY; | ||
| + | if (Math.abs(diff) < 0.5 && Math.abs(sv) < 0.5) { | ||
| + | scrollY = target; | ||
| + | render(); | ||
| + | springRunning = false; | ||
| + | return; | ||
| + | } | ||
| + | sv += diff * SPRING; | ||
| + | sv *= DAMP; | ||
| + | scrollY += sv; | ||
| + | render(); | ||
| + | requestAnimationFrame(step); | ||
| + | } | ||
| + | requestAnimationFrame(step); | ||
| + | } | ||
| + | |||
| + | // ── CONTENT AREA ─────────────────────────────────────── | ||
| + | const splash | ||
| + | const content = document.getElementById(' | ||
| function showSplash() { | function showSplash() { | ||
| Line 660: | Line 673: | ||
| content.classList.add(' | content.classList.add(' | ||
| } | } | ||
| - | + | function showCat(cat) { | |
| - | function showCat(catId) { | + | |
| - | const cat = CATS.find(c => c.id === catId); | + | |
| splash.classList.add(' | splash.classList.add(' | ||
| content.classList.remove(' | content.classList.remove(' | ||
| - | + | | |
| - | | + | |
| document.getElementById(' | document.getElementById(' | ||
| - | document.getElementById(' | + | document.getElementById(' |
| - | + | document.getElementById(' | |
| - | | + | document.getElementById(' |
| - | grid.innerHTML = ''; | + | document.getElementById(' |
| - | | + | |
| - | const card = document.createElement(' | + | |
| - | card.className = ' | + | |
| - | card.href = `/ | + | |
| - | card.innerHTML = `<div class=" | + | |
| - | card.addEventListener(' | + | |
| - | grid.appendChild(card); | + | |
| - | }); | + | |
| - | + | ||
| - | const btn = document.getElementById(' | + | |
| - | btn.href = `/ | + | |
| - | document.getElementById(' | + | |
| } | } | ||
| - | + | function showSub(cat, sub) { | |
| - | function showSub(catId, subId) { | + | |
| - | const cat = CATS.find(c => c.id === catId); | + | |
| - | const sub = cat.subs.find(s => s.id === subId); | + | |
| splash.classList.add(' | splash.classList.add(' | ||
| content.classList.remove(' | content.classList.remove(' | ||
| - | + | | |
| - | | + | |
| document.getElementById(' | document.getElementById(' | ||
| - | document.getElementById(' | + | document.getElementById(' |
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | } | ||
| - | | + | // ── SCROLL ───────────────────────────────────────────── |
| - | | + | const wrap = document.getElementById(' |
| - | [' | + | let momentumRunning |
| - | const card = document.createElement(' | + | |
| - | card.className = ' | + | |
| - | card.href = `/ | + | |
| - | card.innerHTML = `<div class=" | + | |
| - | grid.appendChild(card); | + | |
| - | }); | + | |
| - | | + | // Wheel sensitivity: |
| - | btn.href = `/wiki/doku.php?id=${sub.wikiId}`; | + | const WHEEL_ACCEL |
| - | document.getElementById(' | + | const FRICTION |
| - | } | + | const VEL_CAP |
| + | const VEL_STOP | ||
| - | function | + | function |
| - | | + | |
| - | | + | momentumRunning |
| - | if (tiles.length) tiles[0].scrollIntoView({ behavior: ' | + | function step() |
| - | }, 50); | + | |
| + | if (Math.abs(velocity) < VEL_STOP) | ||
| + | velocity = 0; | ||
| + | momentumRunning = false; | ||
| + | return; | ||
| + | | ||
| + | scrollY += velocity; | ||
| + | render(); | ||
| + | requestAnimationFrame(step); | ||
| + | } | ||
| + | requestAnimationFrame(step); | ||
| } | } | ||
| - | // Cycle through video sources if multiple exist | + | // WHEEL — accumulate velocity proportional to scroll speed |
| - | const video = document.getElementById('ut-video'); | + | wrap.addEventListener('wheel', e => { |
| - | const sources | + | e.preventDefault(); |
| - | | + | const scale = e.deltaMode === 1 ? 20 : e.deltaMode === 2 ? 300 : 1; |
| - | | + | const delta = e.deltaY * scale; |
| - | '/ | + | // Add a fraction of delta to velocity — proportional feel |
| - | | + | velocity += delta * WHEEL_ACCEL; |
| - | ]; | + | velocity = Math.max(-VEL_CAP, Math.min(VEL_CAP, |
| - | let vidIdx | + | |
| - | video.addEventListener(' | + | }, { passive: false }); |
| - | | + | |
| - | | + | // DRAG |
| - | | + | wrap.addEventListener(' |
| + | | ||
| + | velocity = 0; | ||
| + | isDragging = true; lastDragY = e.clientY; lastDragT = performance.now(); | ||
| + | wrap.style.cursor = 'grabbing'; | ||
| + | e.preventDefault(); | ||
| + | }); | ||
| + | window.addEventListener(' | ||
| + | | ||
| + | const now = performance.now(); | ||
| + | const dy = e.clientY - lastDragY; | ||
| + | const dt = Math.max(now - lastDragT, 1); | ||
| + | | ||
| + | | ||
| + | lastDragY = e.clientY; lastDragT = now; | ||
| + | render(); | ||
| + | }); | ||
| + | window.addEventListener(' | ||
| + | | ||
| + | | ||
| + | | ||
| + | startMomentum(); | ||
| }); | }); | ||
| - | searchInput.addEventListener(' | + | // TOUCH |
| + | wrap.addEventListener(' | ||
| + | momentumRunning = false; velocity = 0; | ||
| + | isDragging = true; lastDragY = e.touches[0].clientY; | ||
| + | }, { passive: true }); | ||
| + | wrap.addEventListener(' | ||
| + | if (!isDragging) return; | ||
| + | const now = performance.now(); | ||
| + | const dy = e.touches[0].clientY - lastDragY; | ||
| + | velocity | ||
| + | scrollY | ||
| + | lastDragY = e.touches[0].clientY; | ||
| + | render(); | ||
| + | }, { passive: true }); | ||
| + | wrap.addEventListener(' | ||
| + | |||
| + | // ── SEARCH ───────────────────────────────────────────── | ||
| + | document.getElementById(' | ||
| + | filter = e.target.value; | ||
| + | render(); | ||
| + | }); | ||
| - | // Initial | + | // ── INIT ─────────────────────────────────────────────── |
| - | buildTiles(); | + | window.addEventListener(' |
| + | // start scroll so first item is near center | ||
| + | scrollY = -(document.getElementById(' | ||
| + | render(); | ||
| })(); | })(); | ||