universaltech:start

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
universaltech:start [2026/06/22 06:13] jumpstartadminuniversaltech:start [2026/06/22 06:57] (current) jumpstartadmin
Line 6: Line 6:
 <meta charset="UTF-8"> <meta charset="UTF-8">
 <style> <style>
-/===== UNIVERSAL TECH PAGE ===== */ +body.mode_show #dokuwiki__site { display: none !important; } 
-#dokuwiki__site { display: none !important; }+{ box-sizing: border-box; margin: 0; padding: 0; } 
 +.navbar.navbar-default { z-index: 10000 !important; } 
 +body:not(.mode_show) #ut-page { display: none !important; }
  
 #ut-page { #ut-page {
   position: fixed;   position: fixed;
-  inset: 0; +  top: 0; 
-  z-index: 9999;+  left: 0; right: 0; bottom: 0; 
 +  z-index: 9000;
   background: #0A0816;   background: #0A0816;
   display: flex;   display: flex;
   overflow: hidden;   overflow: hidden;
   font-family: 'Forevs', sans-serif;   font-family: 'Forevs', sans-serif;
 +  color: #EDE8F5;
 } }
  
-/* ===== VIDEO BACKGROUND ===== */+/* VIDEO BG */
 #ut-video-bg { #ut-video-bg {
   position: absolute;   position: absolute;
Line 26: Line 30:
 } }
 #ut-video-bg video { #ut-video-bg video {
-  width: 100%; +  width: 100%; height: 100%;
-  height: 100%;+
   object-fit: cover;   object-fit: cover;
-  opacity: 0.35;+  opacity: 0.55;
 } }
 #ut-video-bg::after { #ut-video-bg::after {
Line 35: Line 38:
   position: absolute;   position: absolute;
   inset: 0;   inset: 0;
-  background: linear-gradient(to right, rgba(10,8,22,0.850%, rgba(10,8,22,0.450%, rgba(10,8,22,0.1) 100%);+  background: linear-gradient(to right, rgba(10,8,22,0.9520%, rgba(10,8,22,0.5555%, rgba(10,8,22,0.1) 100%);
 } }
  
-/* ===== SIDEBAR ===== */ +/* LEFT */ 
-#ut-sidebar {+#ut-left {
   position: relative;   position: relative;
   z-index: 10;   z-index: 10;
-  width: 340px; 
-  flex-shrink: 0; 
-  display: flex; 
-  flex-direction: column; 
-  padding: 28px 0 28px 28px; 
-  gap: 0; 
-  overflow: hidden; 
-} 
- 
-#ut-sidebar-header { 
-  padding: 0 20px 20px 0; 
-  flex-shrink: 0; 
-} 
- 
-#ut-logo { 
-  font-size: 11px; 
-  letter-spacing: 0.18em; 
-  text-transform: uppercase; 
-  color: #6B4FBB; 
-  margin-bottom: 6px; 
-} 
- 
-#ut-sidebar-header h1 { 
-  font-size: 26px; 
-  color: #EDE8F5; 
-  font-weight: 700; 
-  letter-spacing: -0.5px; 
-  line-height: 1; 
-  margin-bottom: 14px; 
-} 
- 
-#ut-search { 
-  width: 100%; 
-  background: rgba(255,255,255,0.06); 
-  border: 1px solid rgba(108,79,187,0.4); 
-  border-radius: 10px; 
-  padding: 9px 14px; 
-  color: #EDE8F5; 
-  font-size: 13px; 
-  font-family: 'Forevs', sans-serif; 
-  outline: none; 
-  transition: border-color 0.15s; 
-} 
-#ut-search::placeholder { color: rgba(108,79,187,0.6); } 
-#ut-search:focus { border-color: rgba(184,173,219,0.6); } 
- 
-/* ===== TILE LIST ===== */ 
-#ut-tile-list { 
   flex: 1;   flex: 1;
-  overflow-y: auto; 
-  overflow-x: hidden; 
-  padding-right: 20px; 
   display: flex;   display: flex;
   flex-direction: column;   flex-direction: column;
-  gap8px+  justify-contentflex-end
-  padding-bottom20px;+  padding: 0 0 64px 64px; 
 +  pointer-eventsnone;
 } }
-#ut-tile-list::-webkit-scrollbar { width: 3px; } 
-#ut-tile-list::-webkit-scrollbar-track { background: transparent; } 
-#ut-tile-list::-webkit-scrollbar-thumb { background: #3D2B7A; border-radius: 2px; } 
  
-/* ===== CATEGORY TILE ===== */ +#ut-splash, #ut-content 
-.ut-cat-wrap { +  transition: opacity 0.35stransform 0.35s;
-  flex-shrink: 0; +
-  transition: margin 0.3s cubic-bezier(0.4,0,0.2,1);+
 } }
- +#ut-splash.hidden, #ut-content.hidden {
-.ut-cat-tile { +
-  position: relative; +
-  border-radius: 14px; +
-  overflow: hidden; +
-  cursor: pointer; +
-  transition: transform 0.25s cubic-bezier(0.4,0,0.2,1), opacity 0.25s, height 0.3s cubic-bezier(0.4,0,0.2,1); +
-  height: 72px; +
-  border: 1px solid rgba(108,79,187,0.25); +
-  user-select: none; +
-+
- +
-.ut-cat-tile.active { +
-  border-color: rgba(184,173,219,0.5); +
-  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:hover .ut-cat-tile-bg { opacity: 0.28; } +
-.ut-cat-tile.active .ut-cat-tile-bg { opacity: 0.32; } +
- +
-.ut-cat-tile-gradient { +
-  position: absolute; +
-  inset: 0; +
-  background: linear-gradient(105deg, rgba(10,8,22,0.72) 0%, rgba(10,8,22,0.3) 100%); +
-+
-.ut-cat-tile.active .ut-cat-tile-gradient { +
-  background: linear-gradient(105deg, rgba(61,43,122,0.6) 0%, rgba(26,20,51,0.3) 100%); +
-+
- +
-.ut-cat-tile-content { +
-  position: relative; +
-  z-index: 2; +
-  display: flex; +
-  align-items: center; +
-  gap: 14px; +
-  height: 100%; +
-  padding: 0 18px; +
-  transition: padding 0.25s; +
-+
- +
-.ut-cat-icon { +
-  font-size: 22px; +
-  color: #9D85DB; +
-  flex-shrink: 0; +
-  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: 600; +
-  color: #C8BFED; +
-  letter-spacing: 0.01em; +
-  transition: font-size 0.25scolor 0.2s; +
-  white-space: nowrap; +
-  overflow: hidden; +
-  text-overflow: ellipsis; +
-+
-.ut-cat-tile.active .ut-cat-label { color: #EDE8F5; font-size: 16px; } +
-.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: 0; +
-+
-.ut-cat-tile.active .ut-cat-chevron { transform: rotate(90deg); color: #9D85DB; } +
- +
-/* ===== SUB-TILES ===== */ +
-.ut-sub-list { +
-  display: flex; +
-  flex-direction: column; +
-  gap: 5px; +
-  overflow: hidden+
-  max-height: 0; +
-  transition: max-height 0.35s cubic-bezier(0.4,0,0.2,1), margin-top 0.25s; +
-  margin-top: 0; +
-+
-.ut-sub-list.open { +
-  max-height: 600px; +
-  margin-top: 6px; +
-+
- +
-.ut-sub-tile { +
-  border-radius: 10px; +
-  height: 46px; +
-  display: flex; +
-  align-items: center; +
-  gap: 12px; +
-  padding: 0 16px 0 24px; +
-  cursor: pointer; +
-  background: rgba(61,43,122,0.22); +
-  border: 1px solid rgba(108,79,187,0.2); +
-  transition: background 0.15s, border-color 0.15s, transform 0.15s; +
-  transform: translateX(-20px);+
   opacity: 0;   opacity: 0;
-  animation: none; +  transform: translateY(12px);
-+
-.ut-sub-list.open .ut-sub-tile { +
-  animation: subSlideIn 0.3s cubic-bezier(0.4,0,0.2,1) forwards; +
-+
-.ut-sub-list.open .ut-sub-tile:nth-child(1) { animation-delay: 0.04s; } +
-.ut-sub-list.open .ut-sub-tile:nth-child(2) { animation-delay: 0.09s; } +
-.ut-sub-list.open .ut-sub-tile:nth-child(3) { animation-delay: 0.14s; } +
-.ut-sub-list.open .ut-sub-tile:nth-child(4) { animation-delay: 0.19s; } +
-.ut-sub-list.open .ut-sub-tile:nth-child(5) { animation-delay: 0.24s; } +
-.ut-sub-list.open .ut-sub-tile:nth-child(6) { animation-delay: 0.29s; } +
-@keyframes subSlideIn { +
-  to { transform: translateX(0); opacity: 1; } +
-+
- +
-.ut-sub-tile:hover { +
-  background: rgba(108,79,187,0.3); +
-  border-color: rgba(184,173,219,0.35); +
-  transform: translateX(3px) !important; +
-+
-.ut-sub-tile.active { +
-  background: rgba(108,79,187,0.38); +
-  border-color: rgba(184,173,219,0.55); +
-  transform: translateX(3px) !important; +
-+
- +
-.ut-sub-dot { +
-  width: 6px; +
-  height: 6px; +
-  border-radius: 50%; +
-  background: #6B4FBB; +
-  flex-shrink: 0; +
-  transition: background 0.15s; +
-+
-.ut-sub-tile.active .ut-sub-dot, +
-.ut-sub-tile:hover .ut-sub-dot { background: #B8ADDB; } +
- +
-.ut-sub-label { +
-  font-size: 13px; +
-  color: #9D85DB; +
-  flex: 1; +
-  transition: color 0.15s; +
-+
-.ut-sub-tile.active .ut-sub-label, +
-.ut-sub-tile:hover .ut-sub-label { color: #EDE8F5; } +
- +
-.ut-sub-arrow { +
-  font-size: 12px; +
-  color: #6B4FBB; +
-  opacity: 0; +
-  transition: opacity 0.15s; +
-+
-.ut-sub-tile:hover .ut-sub-arrow, +
-.ut-sub-tile.active .ut-sub-arrow { opacity: 1; } +
- +
-/* ===== MAIN AREA ===== */ +
-#ut-main { +
-  position: relative; +
-  z-index: 5; +
-  flex: 1; +
-  display: flex; +
-  flex-direction: column; +
-  align-items: flex-start; +
-  justify-content: flex-end; +
-  padding: 0 60px 60px 40px;+
   pointer-events: none;   pointer-events: none;
 +  position: absolute;
 } }
 +#ut-content { pointer-events: all; }
  
-/* splash state */ +.ut-eyebrow {
-#ut-splash { +
-  transition: opacity 0.3s, transform 0.3s; +
-+
-#ut-splash.hidden { opacity: 0; transform: translateY(10px); pointer-events: none; } +
- +
-#ut-splash-tag {+
   font-size: 11px;   font-size: 11px;
-  letter-spacing: 0.18em;+  letter-spacing: 0.2em;
   text-transform: uppercase;   text-transform: uppercase;
   color: #6B4FBB;   color: #6B4FBB;
Line 308: Line 72:
 } }
 #ut-splash-title { #ut-splash-title {
-  font-size: 64px;+  font-size: 72px;
   font-weight: 700;   font-weight: 700;
 +  line-height: 1;
 +  letter-spacing: -1.5px;
 +  margin-bottom: 18px;
   color: #EDE8F5;   color: #EDE8F5;
-  line-height: 1; 
-  letter-spacing: -1px; 
-  margin-bottom: 16px; 
 } }
 #ut-splash-title em { color: #6B4FBB; font-style: normal; } #ut-splash-title em { color: #6B4FBB; font-style: normal; }
Line 319: Line 83:
   font-size: 15px;   font-size: 15px;
   color: #9D85DB;   color: #9D85DB;
-  max-width: 420px+  max-width: 400px
-  line-height: 1.6; +  line-height: 1.65;
-  margin-bottom: 28px; +
-+
-#ut-splash-pills { +
-  display: flex; +
-  flex-wrap: wrap; +
-  gap: 8px; +
-  pointer-events: all; +
-+
-.ut-splash-pill { +
-  background: rgba(61,43,122,0.4); +
-  border: 1px solid rgba(108,79,187,0.45); +
-  border-radius: 20px; +
-  padding: 6px 16px; +
-  font-size: 12px; +
-  color: #B8ADDB; +
-  cursor: pointer; +
-  transition: all 0.15s; +
-  font-family: 'Forevs', sans-serif; +
-+
-.ut-splash-pill:hover { +
-  background: rgba(108,79,187,0.45); +
-  color: #EDE8F5; +
-  border-color: rgba(184,173,219,0.6);+
 } }
  
 /* content state */ /* content state */
-#ut-content { 
-  position: absolute; 
-  bottom: 60px; 
-  left: 40px; 
-  right: 60px; 
-  transition: opacity 0.3s, transform 0.3s; 
-  pointer-events: all; 
-} 
-#ut-content.hidden { opacity: 0; transform: translateY(10px); pointer-events: none; } 
- 
 #ut-breadcrumb { #ut-breadcrumb {
   font-size: 11px;   font-size: 11px;
-  letter-spacing: 0.12em;+  letter-spacing: 0.14em;
   text-transform: uppercase;   text-transform: uppercase;
   color: #6B4FBB;   color: #6B4FBB;
Line 365: Line 96:
 } }
 #ut-content-title { #ut-content-title {
-  font-size: 48px;+  font-size: 56px;
   font-weight: 700;   font-weight: 700;
-  color: #EDE8F5; +  letter-spacing: -1px;
-  letter-spacing: -0.5px;+
   line-height: 1;   line-height: 1;
-  margin-bottom: 10px;+  margin-bottom: 12px; 
 +  color: #EDE8F5;
 } }
 #ut-content-desc { #ut-content-desc {
   font-size: 14px;   font-size: 14px;
   color: #9D85DB;   color: #9D85DB;
-  max-width: 500px+  max-width: 420px
-  line-height: 1.6;+  line-height: 1.65;
   margin-bottom: 24px;   margin-bottom: 24px;
 } }
Line 382: Line 113:
   display: flex;   display: flex;
   flex-wrap: wrap;   flex-wrap: wrap;
-  gap: 10px+  gap: 8px
-  margin-bottom: 20px;+  margin-bottom: 22px; 
 +  pointer-events: all;
 } }
 .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,79,187,0.35); +  border: 1px solid rgba(108,79,187,0.3); 
-  border-radius: 12px+  border-radius: 10px
-  padding: 14px 18px;+  padding: 12px 16px;
   cursor: pointer;   cursor: pointer;
   transition: all 0.15s;   transition: all 0.15s;
-  backdrop-filter: blur(6px); 
   text-decoration: none;   text-decoration: none;
   display: block;   display: block;
-  min-width: 160px; 
 } }
 .ut-guide-card:hover { .ut-guide-card:hover {
-  background: rgba(61,43,122,0.55); +  background: rgba(61,43,122,0.5); 
-  border-color: rgba(184,173,219,0.5);+  border-color: rgba(184,173,219,0.45);
   transform: translateY(-2px);   transform: translateY(-2px);
 } }
-.ut-guide-card-name { +.ut-guide-card-name { font-size: 13px; font-weight: 600; color: #C8BFED; margin-bottom: 3px; } 
-  font-size: 13px; +.ut-guide-card-meta { font-size: 11px; color: #6B4FBB; }
-  font-weight: 600; +
-  color: #C8BFED; +
-  margin-bottom: 3px; +
-+
-.ut-guide-card-meta { +
-  font-size: 11px; +
-  color: #6B4FBB; +
-}+
  
 #ut-open-btn { #ut-open-btn {
Line 417: Line 139:
   align-items: center;   align-items: center;
   gap: 8px;   gap: 8px;
-  background: rgba(108,79,187,0.3); +  background: rgba(108,79,187,0.28); 
-  border: 1px solid rgba(108,79,187,0.55);+  border: 1px solid rgba(108,79,187,0.5);
   border-radius: 10px;   border-radius: 10px;
   padding: 10px 20px;   padding: 10px 20px;
Line 425: Line 147:
   font-family: 'Forevs', sans-serif;   font-family: 'Forevs', sans-serif;
   cursor: pointer;   cursor: pointer;
-  transition: all 0.15s; 
   text-decoration: none;   text-decoration: none;
 +  transition: all 0.15s;
 +  pointer-events: all;
 } }
 #ut-open-btn:hover { #ut-open-btn:hover {
-  background: rgba(108,79,187,0.55);+  background: rgba(108,79,187,0.5);
   color: #EDE8F5;   color: #EDE8F5;
   border-color: rgba(184,173,219,0.6);   border-color: rgba(184,173,219,0.6);
 } }
  
-/* ===== CATEGORY TILE ACCENT COLORS ===== */ +/* RIGHT — osu carousel */ 
-.ut-accent-movement .ut-cat-tile-bg backgroundlinear-gradient(135deg, #3D2B7A, #1A1433)} +#ut-carousel-wrap { 
-.ut-accent-combat .ut-cat-tile-bg { backgroundlinear-gradient(135deg, #7A2B4A, #3A1020)} +  positionrelative
-.ut-accent-items .ut-cat-tile-bg { backgroundlinear-gradient(135deg, #2B5A7A, #0F2535)} +  z-index10
-.ut-accent-map .ut-cat-tile-bg { backgroundlinear-gradient(135deg, #2B7A4A, #0F3520)} +  width500px
-.ut-accent-mechanics .ut-cat-tile-bg { backgroundlinear-gradient(135deg, #7A5A2B, #352510)} +  flex-shrink0
-.ut-accent-abilities .ut-cat-tile-bg { backgroundlinear-gradient(135deg, #5A2B7A, #251035); }+  overflow: hidden; 
 +  display: flex; 
 +  align-itemscenter
 +  user-selectnone; 
 +}
  
 +#ut-carousel {
 +  position: absolute;
 +  right: 0;
 +  width: 100%;
 +}
 +
 +/* tiles */
 +.ut-item {
 +  position: absolute;
 +  right: 0;
 +  border-radius: 16px 0 0 16px;
 +  overflow: hidden;
 +  cursor: pointer;
 +  border: 1px solid rgba(108,79,187,0.2);
 +  border-right: none;
 +  will-change: transform, top, height, width, opacity;
 +  transition:
 +    width 0.22s cubic-bezier(0.4,0,0.2,1),
 +    height 0.22s cubic-bezier(0.4,0,0.2,1),
 +    opacity 0.22s;
 +}
 +
 +.ut-item.is-cat        { width: 420px; height: 80px; }
 +.ut-item.is-sub        { width: 370px; height: 60px; }
 +.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:hover .ut-item-bg { opacity: 0.3; }
 +.ut-item.selected .ut-item-bg { opacity: 0.38; }
 +
 +.ut-item-overlay {
 +  position: absolute; inset: 0;
 +  background: linear-gradient(90deg, rgba(10,8,22,0.85) 0%, rgba(10,8,22,0.25) 100%);
 +}
 +
 +.ut-item-inner {
 +  position: relative;
 +  z-index: 2;
 +  display: flex;
 +  align-items: center;
 +  gap: 16px;
 +  padding: 0 22px;
 +  width: 100%;
 +  height: 100%;
 +}
 +
 +.ut-item-icon {
 +  font-size: 22px;
 +  color: #9D85DB;
 +  flex-shrink: 0;
 +  transition: color 0.2s, font-size 0.2s;
 +}
 +.ut-item.selected .ut-item-icon  { color: #C8BFED; font-size: 24px; }
 +.ut-item.is-sub .ut-item-icon    { font-size: 15px; color: #6B4FBB; }
 +.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: 600;
 +  color: #C8BFED;
 +  white-space: nowrap;
 +  overflow: hidden;
 +  text-overflow: ellipsis;
 +  transition: color 0.2s, font-size 0.2s;
 +}
 +.ut-item.selected .ut-item-label     { color: #EDE8F5; font-size: 17px; }
 +.ut-item.is-sub .ut-item-label       { font-size: 13px; color: #9D85DB; font-weight: 500; }
 +.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: 16px 0 0 16px;
 +  opacity: 0;
 +  transition: opacity 0.2s;
 +}
 +.ut-item.selected .ut-item-accent-bar { opacity: 1; }
 +
 +/* accent colors */
 +.ut-acc-movement .ut-item-bg  { background: linear-gradient(90deg,#3D2B7A,#6B4FBB); }
 +.ut-acc-movement .ut-item-accent-bar { background: #9D85DB; }
 +.ut-acc-combat .ut-item-bg    { background: linear-gradient(90deg,#7A2B4A,#BB4F7A); }
 +.ut-acc-combat .ut-item-accent-bar   { background: #DB85AD; }
 +.ut-acc-items .ut-item-bg     { background: linear-gradient(90deg,#1E5A7A,#2B8FBB); }
 +.ut-acc-items .ut-item-accent-bar    { background: #85CADB; }
 +.ut-acc-map .ut-item-bg       { background: linear-gradient(90deg,#1E7A4A,#2BBB6F); }
 +.ut-acc-map .ut-item-accent-bar      { background: #85DBA8; }
 +.ut-acc-mechanics .ut-item-bg { background: linear-gradient(90deg,#7A5A1E,#BB8F2B); }
 +.ut-acc-mechanics .ut-item-accent-bar{ background: #DBC085; }
 +.ut-acc-abilities .ut-item-bg { background: linear-gradient(90deg,#5A1E7A,#8F2BBB); }
 +.ut-acc-abilities .ut-item-accent-bar{ background: #C085DB; }
 +
 +/* search */
 +#ut-search-wrap {
 +  position: absolute;
 +  top: 24px; right: 0;
 +  width: 440px;
 +  z-index: 20;
 +}
 +#ut-search {
 +  width: 100%;
 +  background: rgba(10,8,22,0.8);
 +  border: 1px solid rgba(108,79,187,0.4);
 +  border-right: none;
 +  border-radius: 10px 0 0 10px;
 +  padding: 10px 16px;
 +  color: #EDE8F5;
 +  font-size: 13px;
 +  font-family: 'Forevs', sans-serif;
 +  outline: none;
 +  transition: border-color 0.15s;
 +}
 +#ut-search::placeholder { color: rgba(108,79,187,0.55); }
 +#ut-search:focus { border-color: rgba(184,173,219,0.55); }
 </style> </style>
 </head> </head>
 <body> <body>
- 
 <div id="ut-page"> <div id="ut-page">
  
-  <!-- VIDEO BACKGROUND --> 
   <div id="ut-video-bg">   <div id="ut-video-bg">
     <video autoplay muted loop playsinline id="ut-video">     <video autoplay muted loop playsinline id="ut-video">
-      <source src="/wiki/lib/tpl/bootstrap3/videos/gameplay1.mp4" type="video/mp4"> +      <source src="/wiki/lib/exe/fetch.php?media=0%3Awiki1.mp4" type="video/mp4">
-      <source src="/wiki/lib/tpl/bootstrap3/videos/gameplay2.mp4" type="video/mp4">+
     </video>     </video>
   </div>   </div>
  
-  <!-- SIDEBAR --> +  <div id="ut-left">
-  <div id="ut-sidebar"> +
-    <div id="ut-sidebar-header"> +
-      <div id="ut-logo">JumpStart Wiki</div> +
-      <h1>Universal Tech</h1> +
-      <input type="text" id="ut-search" placeholder="Search tech..." autocomplete="off" /> +
-    </div> +
-    <div id="ut-tile-list"></div> +
-  </div> +
- +
-  <!-- MAIN AREA --> +
-  <div id="ut-main"> +
     <div id="ut-splash">     <div id="ut-splash">
-      <div id="ut-splash-tag">JumpStart Wiki · Universal Tech</div>+      <div class="ut-eyebrow">JumpStart Wiki</div>
       <div id="ut-splash-title">Master the<br><em>Fundamentals</em></div>       <div id="ut-splash-title">Master the<br><em>Fundamentals</em></div>
-      <div id="ut-splash-desc">Techniques and mechanics that apply to every hero. Pick a category to explore.</div> +      <div id="ut-splash-desc">Mechanics and techniques that apply to every hero in Deadlock. Pick a category to explore.</div>
-      <div id="ut-splash-pills" id="quickPills"></div>+
     </div>     </div>
- 
     <div id="ut-content" class="hidden">     <div id="ut-content" class="hidden">
       <div id="ut-breadcrumb"></div>       <div id="ut-breadcrumb"></div>
Line 481: Line 318:
       <div id="ut-content-desc"></div>       <div id="ut-content-desc"></div>
       <div id="ut-card-grid"></div>       <div id="ut-card-grid"></div>
-      <a id="ut-open-btn" href="#"> +      <a id="ut-open-btn" href="#"><i class="ti ti-external-link"></i><span id="ut-btn-label">Open wiki page</span></a>
-        <i class="ti ti-external-link"></i> +
-        <span id="ut-btn-label">Open wiki page</span> +
-      </a>+
     </div>     </div>
 +  </div>
  
 +  <div id="ut-carousel-wrap">
 +    <div id="ut-search-wrap">
 +      <input type="text" id="ut-search" placeholder="Search tech..." autocomplete="off" />
 +    </div>
 +    <div id="ut-carousel"></div>
   </div>   </div>
-</div> 
  
 +</div>
 <script> <script>
 (function(){ (function(){
  
 const CATS = [ const CATS = [
-  { +  { id:'movement', icon:'ti-run', label:'Movement', acc:'movement', 
-    id: 'movement', icon: 'ti-run', label: 'Movement', accent: 'movement', +    desc:'Core ground movement — walkingsprinting, crouching, sliding, jumping, and stamina management.', 
-    desc: 'Core locomotion mechanics — how you movefly, and traverse the map at speed.', +    wikiId:'universaltech:movement', 
-    wikiId: 'universaltech:movement', +    subs:[ 
-    subs: [ +      {id:'walking',   label:'Walking',   desc:'Base movement speed and mechanics.', wikiId:'universaltech:movement:walking'}, 
-      { id: 'bunnyhop', label: 'Bunny Hop', desc: 'Speed maintenance between jumps.', wikiId: 'universaltech:movement:bunnyhop' }, +      {id:'sprinting', label:'Sprinting', desc:'Sprint mechanics and speed thresholds.', wikiId:'universaltech:movement:sprinting'}, 
-      { id: 'airstrafing', label: 'Air Strafing', desc: 'Directional control while airborne.', wikiId: 'universaltech:movement:airstrafing' }, +      {id:'crouching', label:'Crouching', desc:'Crouch movement and hitbox changes.', wikiId:'universaltech:movement:crouching'}, 
-      { id: 'walljump', label: 'Wall Jump', desc: 'Bouncing off geometry for height or distance.', wikiId: 'universaltech:movement:walljump' }, +      {id:'sliding',   label:'Sliding',   desc:'Slide mechanics, speed, and timing.', wikiId:'universaltech:movement:sliding'}, 
-      { id: 'slidehop', label: 'Slide Hop', desc: 'Chaining slides and hops for momentum.', wikiId: 'universaltech:movement:slidehop' }, +      {id:'stamina',   label:'Stamina',   desc:'Stamina system, regeneration, and management.', wikiId:'universaltech:movement:stamina'}, 
-      { id: 'zipline', label: 'Zipline Tech', desc: 'Advanced entryexit, and speed tricks.', wikiId: 'universaltech:movement:zipline' }, +      {id:'jumping',   label:'Jumping',   desc:'Jump mechanicsheight, and timing.', wikiId:'universaltech:movement:jumping'}, 
-    ] +    ]}, 
-  }, +  { id:'dashing', icon:'ti-player-skip-forward', label:'Dashing', acc:'combat', 
-  { +    desc:'Dash techs — directional dashes, aerial dash combos, and advanced dash cancels.', 
-    id: 'combat', icon: 'ti-crosshair', label: 'Combat', accent: 'combat', +    wikiId:'universaltech:dashing', 
-    desc: 'Engagement techniques — positioningtiming, burst windows, and aim mechanics.', +    subs:[ 
-    wikiId: 'universaltech:combat', +      {id:'strafedash', label:'Strafe Dashing',         desc:'Dashing at angles for lateral speed.', wikiId:'universaltech:dashing:strafedash'}, 
-    subs: [ +      {id:'dashjump',   label:'Dash Jumping',           desc:'Combining a dash into a jump for distance.', wikiId:'universaltech:dashing:dashjump'}, 
-      { id: 'peekshoot', label: 'Peek Shooting', desc: 'Minimize exposure while landing shots.', wikiId: 'universaltech:combat:peekshoot' }, +      {id:'dashslide',  label:'Dash Sliding',           desc:'Cancelling a dash into a slide.', wikiId:'universaltech:dashing:dashslide'}, 
-      { id: 'jumpshoot', label: 'Jump Shooting', desc: 'Accuracy and timing when airborne.', wikiId: 'universaltech:combat:jumpshoot' }, +      {id:'iad',        label:'Instant Air Dash (I.A.D)',desc:'Minimal-height air dash for fast horizontal movement.', wikiId:'universaltech:dashing:iad'}, 
-      { id: 'slideshoot', label: 'Slide Shooting', desc: 'Aim control through slide animations.', wikiId: 'universaltech:combat:slideshoot' }, +      {id:'wallstrafe', label:'Wall Strafe',            desc:'Dashing along walls for momentum.', wikiId:'universaltech:dashing:wallstrafe'}, 
-      { id: 'reloadcancel', label: 'Reload Cancel', desc: 'Safely cutting reload animations short.', wikiId: 'universaltech:combat:reloadcancel' }, +    ]}, 
-    ] +  { id:'mantling', icon:'ti-arrow-bar-up', label:'Mantling', acc:'map', 
-  }, +    desc:'Mantle techs — advanced ledge grabssuper glides, and mantle-based launches.', 
-  +    wikiId:'universaltech:mantling', 
-    id: 'items', icon: 'ti-package', label: 'Item Tech', accent: 'items', +    subs:[ 
-    desc: 'Hidden mechanics and advanced interactions with universal items.', +      {id:'mantleslide',  label:'Mantle Slide',      desc:'Sliding immediately out of a mantle.', wikiId:'universaltech:mantling:mantleslide'}, 
-    wikiId: 'universaltech:items', +      {id:'superglide',   label:'Super Glide',       desc:'Gaining extra airtime off a mantle.', wikiId:'universaltech:mantling:superglide'}, 
-    subs: [ +      {id:'glidewj',      label:'Glide Wall Jump',   desc:'Wall jumping during a glide for height.', wikiId:'universaltech:mantling:glidewj'}, 
-      { id: 'warpstone', label: 'Warp Stone', desc: 'Positioning tricks and cancel windows.', wikiId: 'universaltech:items:warpstone' }, +      {id:'mantlestrafe', label:'Mantle Strafe',     desc:'Angling movement during mantle animation.', wikiId:'universaltech:mantling:mantlestrafe'}, 
-      { id: 'cloakmantle', label: 'Cloak + Mantle', desc: 'Invisibility combined with mobility items.', wikiId: 'universaltech:items:cloakmantle' }, +      {id:'fakemantle',   label:'Fake Mantle',       desc:'Cancelling the mantle animation early.', wikiId:'universaltech:mantling:fakemantle'}, 
-      { id: 'coldfront', label: 'Cold Front', desc: 'Slow tech and zone control setups.', wikiId: 'universaltech:items:coldfront' }, +      {id:'mantlelaunch', label:'Mantle Launch',     desc:'Using mantle momentum to launch far.', wikiId:'universaltech:mantling:mantlelaunch'}, 
-      { id: 'magiccarpet', label: 'Magic Carpet', desc: 'Hover positioning and edge-case uses.', wikiId: 'universaltech:items:magiccarpet' }, +    ]}, 
-    ] +  { id:'ziplines', icon:'ti-line-dashed', label:'Ziplines', acc:'items', 
-  }, +    desc:'Zipline techs — momentum conservation, cancels, jumps, and launch techniques.', 
-  { +    wikiId:'universaltech:ziplines', 
-    id: 'map', icon: 'ti-map', label: 'Map Tech', accent: 'map', +    subs:[ 
-    desc: 'Geometry exploitsmap-specific routes, and positional advantages.', +      {id:'zmc',     label:'Zipline Momentum Conservation (Z.M.C)', desc:'Keeping speed when leaving a zipline.', wikiId:'universaltech:ziplines:zmc'}, 
-    wikiId: 'universaltech:map', +      {id:'zipcancel',label:'Zip Cancel', desc:'Cancelling off a zipline at the right moment.', wikiId:'universaltech:ziplines:zipcancel'}, 
-    subs: [ +      {id:'zipjump', label:'Zip Jump',   desc:'Jumping from a zipline with extra height.', wikiId:'universaltech:ziplines:zipjump'}, 
-      { id: 'roofjumps', label: 'Roof Jumps', desc: 'Common roof access points across all lanes.', wikiId: 'universaltech:map:roofjumps' }, +      {id:'zipdash', label:'Zip Dash',   desc:'Dashing on or off a zipline.', wikiId:'universaltech:ziplines:zipdash'}, 
-      { id: 'shortcuts', label: 'Shortcuts', desc: 'Fast-path routes used in high-level play.', wikiId: 'universaltech:map:shortcuts' }, +      {id:'ziplaunch',label:'Zip Launch',desc:'Using zipline exit for maximum launch speed.', wikiId:'universaltech:ziplines:ziplaunch'}, 
-      { id: 'oob', label: 'Out-of-Bounds Tech', desc: 'Legal geometry clips and unintended spots.', wikiId: 'universaltech:map:oob' }, +    ]}, 
-    ] +  { id:'ropes', icon:'ti-minus-vertical', label:'Ropes', acc:'mechanics', 
-  }, +    desc:'Rope techs — cancels, launches, and wall jumps off rope anchors.', 
-  +    wikiId:'universaltech:ropes', 
-    id: 'mechanics', icon: 'ti-settings', label: 'Game Mechanics', accent: 'mechanics', +    subs:[ 
-    desc: 'Engine and system-level behaviour that affects every hero equally.', +      {id:'ropecancel', label:'Rope Cancel',    desc:'Cancelling rope grab for momentum burst.', wikiId:'universaltech:ropes:ropecancel'}, 
-    wikiId: 'universaltech:mechanics', +      {id:'ropelaunch', label:'Rope Launch',    desc:'Using rope to catapult in a direction.', wikiId:'universaltech:ropes:ropelaunch'}, 
-    subs: [ +      {id:'ropewj',     label:'Rope Wall Jump', desc:'Wall jumping off a rope attachment point.', wikiId:'universaltech:ropes:ropewj'}, 
-      { id: 'animation', label: 'Animation Cancels', desc: 'Breaking animations to act faster.', wikiId: 'universaltech:mechanics:animation' }, +    ]}, 
-      { id: 'momentum', label: 'Momentum Preservation', desc: 'How the engine stores and bleeds speed.', wikiId: 'universaltech:mechanics:momentum' }, +  { id:'vent', icon:'ti-wind', label:'Vent', acc:'abilities', 
-      { id: 'phasing', label: 'Phasing Windows', desc: 'Invincibility and i-frame timing.', wikiId: 'universaltech:mechanics:phasing' }, +    desc:'Vent interactions — boost techs and stun mechanics.', 
-    ] +    wikiId:'universaltech:vent', 
-  }, +    subs:[ 
-  { +      {id:'ventboost', label:'Vent Boost', desc:'Using vents for extra upward velocity.', wikiId:'universaltech:vent:ventboost'}, 
-    id: 'abilities', icon: 'ti-bolt', label: 'Ability Tech', accent: 'abilities', +      {id:'ventstun',  label:'Vent Stun',  desc:'Vent stun timing and counterplay.', wikiId:'universaltech:vent:ventstun'}, 
-    desc: 'Universal interactions between abilities, items, and game systems.', +    ]}, 
-    wikiId: 'universaltech:abilities', +  { id:'melee', icon:'ti-sword', label:'Melee', acc:'combat', 
-    subs: [ +    desc:'Melee techs — light, heavy, parry, cancels, and advanced melee interactions.', 
-      { id: 'quickcast', label: 'Quick Cast', desc: 'Instant ability activation setups.', wikiId: 'universaltech:abilities:quickcast' }, +    wikiId:'universaltech:melee', 
-      { id: 'buffstack', label: 'Buff Stacking', desc: 'Layering buffs for maximum burst.', wikiId: 'universaltech:abilities:buffstack' }, +    subs:[ 
-      { id: 'canceltech', label: 'Cancel Tech', desc: 'Cancelling abilities for free casts.', wikiId: 'universaltech:abilities:canceltech' }, +      {id:'lightmelee', label:'Light Melee',               desc:'Light melee timing and combo use.', wikiId:'universaltech:melee:lightmelee'}, 
-    ] +      {id:'heavymelee', label:'Heavy Melee',               desc:'Heavy melee range, knockback, and setup.', wikiId:'universaltech:melee:heavymelee'}, 
-  },+      {id:'parry',      label:'Parry',                     desc:'Parry window, animation, and rewards.', wikiId:'universaltech:melee:parry'}, 
 +      {id:'hmc',        label:'Heavy Melee Cancel (H.M.C)',desc:'Cancelling heavy melee for free movement.', wikiId:'universaltech:melee:hmc'}, 
 +      {id:'hiddenmelee',label:'Hidden Melee',              desc:'Concealed melee animations and setups.', wikiId:'universaltech:melee:hiddenmelee'}, 
 +      {id:'feign',      label:'Feign',                     desc:'Feinting melee to bait parry responses.', wikiId:'universaltech:melee:feign'}, 
 +    ]}, 
 +  { id:'walljump', icon:'ti-arrow-bounce', label:'Wall Jumping', acc:'movement', 
 +    desc:'Wall jump techs — directional controlcorner and edge boosts, surf, and fatigue management.', 
 +    wikiId:'universaltech:walljump', 
 +    subs:[ 
 +      {id:'wjd',       label:'Wall Jump Direction (W.J.D)', desc:'Controlling angle and direction off walls.', wikiId:'universaltech:walljump:wjd'}, 
 +      {id:'cornerboost',label:'Corner Boost',              desc:'Using corners for extra launch speed.', wikiId:'universaltech:walljump:cornerboost'}, 
 +      {id:'edgeboost',  label:'Edge Boost',                desc:'Boosting off ledge geometry.', wikiId:'universaltech:walljump:edgeboost'}, 
 +      {id:'refundwj',   label:'Refund Window',             desc:'Timing wall jumps to recover stamina.', wikiId:'universaltech:walljump:refundwj'}, 
 +      {id:'wjfatigue',  label:'Wall Jump Fatigue',         desc:'Managing wall jump degradation over time.', wikiId:'universaltech:walljump:wjfatigue'}, 
 +      {id:'wallsurf',   label:'Wall Surf',                 desc:'Riding wall surfaces for sustained speed.', wikiId:'universaltech:walljump:wallsurf'}, 
 +    ]}, 
 +  { id:'strafing', icon:'ti-arrows-left-right', label:'Strafing', acc:'movement', 
 +    desc:'Advanced strafe techs — air strafing, bunnyhopping, and slide bunnyhopping.', 
 +    wikiId:'universaltech:strafing', 
 +    subs:[ 
 +      {id:'airstrafe',   label:'Air Strafing',      desc:'Directional air control for speed and evasion.', wikiId:'universaltech:strafing:airstrafe'}, 
 +      {id:'bunnyhop',    label:'Bunnyhopping',       desc:'Chaining jumps to maintain and build speed.', wikiId:'universaltech:strafing:bunnyhop'}, 
 +      {id:'slidebhop',   label:'Slide Bunnyhopping', desc:'Combining slides and hops for max momentum.', wikiId:'universaltech:strafing:slidebhop'}, 
 +    ]}, 
 +  { id:'itemtech', icon:'ti-package', label:'Item Tech', acc:'items', 
 +    desc:'Universal item interactions and advanced tech with purchasable items.', 
 +    wikiId:'universaltech:itemtech', 
 +    subs:[ 
 +      {id:'majesticleap',  label:'Majestic Leap',    desc:'Launch techs and landing timing.', wikiId:'universaltech:itemtech:majesticleap'}, 
 +      {id:'warpstone',     label:'Warp Stone',        desc:'Positioning tricks and cancel windows.', wikiId:'universaltech:itemtech:warpstone'}, 
 +      {id:'rescuebeam',    label:'Rescue Beam',       desc:'Beam pull interactions and edge cases.', wikiId:'universaltech:itemtech:rescuebeam'}, 
 +      {id:'magiccarpet',   label:'Magic Carpet',      desc:'Hover positioning and advanced uses.', wikiId:'universaltech:itemtech:magiccarpet'}, 
 +      {id:'slowinghex',    label:'Slowing Hex',       desc:'Slow application timing and combos.', wikiId:'universaltech:itemtech:slowinghex'}, 
 +      {id:'staminamaster', label:'Stamina Mastery',   desc:'Maximising stamina regen and usage.', wikiId:'universaltech:itemtech:staminamaster'}, 
 +      {id:'capacitor',     label:'Capacitor',         desc:'Charge timing and burst application.', wikiId:'universaltech:itemtech:capacitor'}, 
 +    ]},
 ]; ];
  
 +const VIDS = [
 +  '/wiki/lib/exe/fetch.php?media=0%3Awiki1.mp4',
 +  '/wiki/lib/exe/fetch.php?media=0%3Awiki2.mp4',
 +  '/wiki/lib/exe/fetch.php?media=0%3Awiki3.mp4',
 +  '/wiki/lib/exe/fetch.php?media=0%3Awiki4.mp4',
 +];
 +let vi = 0;
 +const video = document.getElementById('ut-video');
 +video.addEventListener('ended', () => {
 +  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 document.getElementById('ut-tile-list')+// ── LAYOUT CONSTANTS ──────────────────────────────────── 
-const searchInput document.getElementById('ut-search')+const CAT_H     80
-const splash document.getElementById('ut-splash')+const CAT_H_SEL 96
-const content document.getElementById('ut-content')+const SUB_H     60
-const splashPills document.getElementById('ut-splash-pills');+const SUB_H_SEL 72
 +const GAP       7;
  
-// Build splash pills +// scrollY offset into virtual list (px). Center of viewport scrollY. 
-CATS.forEach(cat => { +let scrollY   0
-  const pill = document.createElement('button')+let velocity  0
-  pill.className 'ut-splash-pill'; +let rafId     = null
-  pill.textContent cat.label+let isDragging = false
-  pill.addEventListener('click', () => selectCat(cat.id))+let lastDragY  0
-  splashPills.appendChild(pill)+let lastDragT  0;
-})+
- +
-function buildTiles(filter) { +
-  tileList.innerHTML ''+
-  const q filter ? filter.toLowerCase() : '';+
  
 +// ── FLAT ITEM LIST ──────────────────────────────────────
 +function buildItemList() {
 +  const items = [];
 +  const q = filter.toLowerCase();
   CATS.forEach(cat => {   CATS.forEach(cat => {
-    const matchCat = !q || cat.label.toLowerCase().includes(q)+    const subMatch cat.subs.filter(s => 
-    const matchSubs = cat.subs.filter(s => !q || s.label.toLowerCase().includes(q) || matchCat); +      !q || s.label.toLowerCase().includes(q) || cat.label.toLowerCase().includes(q) 
-    if (q && matchSubs.length === 0) return;+    ); 
 +    if (q && subMatch.length === 0 && !cat.label.toLowerCase().includes(q)) return; 
 +    items.push({ type:'cat', cat, sub:null }); 
 +    if (activeCatId === cat.id) { 
 +      (q ? subMatch : cat.subs).forEach(sub => items.push({ type:'sub', cat, sub })); 
 +    } 
 +  }); 
 +  return items; 
 +}
  
-    const isActive = activeCatId === cat.id;+// ── POSITIONS ─────────────────────────────────────────── 
 +function computePositions(items) { 
 +  let y = 0; 
 +  return items.map(item => { 
 +    const isSel item.type === 'cat' 
 +      ? activeCatId === item.cat.id && activeSubId === null 
 +      : activeSubId === item.sub.id; 
 +    const h = item.type === 'cat' ? (isSel ? CAT_H_SEL : CAT_H) : (isSel ? SUB_H_SEL : SUB_H); 
 +    const pos = { y, h, isSel }; 
 +    y += h + GAP; 
 +    return pos; 
 +  }); 
 +}
  
-    const wrap = document.createElement('div'); +// ── RENDER ───────────────────────────────────────────── 
-    wrap.className `ut-cat-wrap ut-accent-${cat.accent}`+function render() { 
-    if (!isActive && activeCatId !== nullwrap.classList.add('has-active');+  const carousel = document.getElementById('ut-carousel'); 
 +  const wrapH    document.getElementById('ut-carousel-wrap').offsetHeight
 +  const items    buildItemList(); 
 +  const pos      computePositions(items)
 +  const totalH   = pos.length ? pos[pos.length-1].y + pos[pos.length-1].h : 0;
  
-    // Category tile +  // clamp scroll: allow scrolling through the whole list 
-    const tile document.createElement('div'); +  const minScroll = -(wrapH / 2); 
-    tile.className = 'ut-cat-tile' + (isActive ? ' active' : '') + (activeCatId && !isActive ? ' sibling-shrink' : ''); +  const maxScroll totalH wrapH 2; 
-    tile.innerHTML +  scrollY Math.max(minScroll, Math.min(maxScrollscrollY));
-      <div class="ut-cat-tile-bg"></div> +
-      <div class="ut-cat-tile-gradient"></div> +
-      <div class="ut-cat-tile-content"> +
-        <i class="ti ${cat.icon} ut-cat-icon" aria-hidden="true"></i> +
-        <div class="ut-cat-text"> +
-          <div class="ut-cat-label">${cat.label}</div> +
-          <div class="ut-cat-count">${cat.subs.length} techniques</div> +
-        </div> +
-        <i class="ti ti-chevron-right ut-cat-chevron" aria-hidden="true"></i> +
-      </div> +
-    `; +
-    tile.addEventListener('click'() => selectCat(cat.id));+
  
-    // Sub list +  // track which uids exist 
-    const subList document.createElement('div'); +  const existingEls {}; 
-    subList.className 'ut-sub-list'(isActive ? ' open' : '');+  carousel.querySelectorAll('.ut-item').forEach(el => { existingEls[el.dataset.uid] = el; }); 
 +  const newUids = new Set(); 
 +  items.forEach(item => newUids.add(uid(item))); 
 +  Object.keys(existingEls).forEach(u => { if (!newUids.has(u)) existingEls[u].remove(); });
  
-    const visibleSubs = q ? matchSubs : cat.subs; +  // viewport center 
-    visibleSubs.forEach(sub => { +  const vc wrapH 2;
-      const isActiveSub = activeSubId === sub.id; +
-      const subTile = document.createElement('div'); +
-      subTile.className = 'ut-sub-tile' + (isActiveSub ? ' active' : ''); +
-      subTile.innerHTML = ` +
-        <span class="ut-sub-dot"></span> +
-        <span class="ut-sub-label">${sub.label}</span> +
-        <i class="ti ti-arrow-right ut-sub-arrow" aria-hidden="true"></i> +
-      `; +
-      subTile.addEventListener('click', (e) => { e.stopPropagation(); selectSub(cat.id, sub.id); }); +
-      subList.appendChild(subTile); +
-    });+
  
-    wrap.appendChild(tile); +  items.forEach((item, i) => { 
-    wrap.appendChild(subList); +    const u = uid(item); 
-    tileList.appendChild(wrap);+    const { y: iy, h, isSel } = pos[i]; 
 + 
 +    // position relative to viewport center 
 +    const screenY = iy - scrollY; 
 +    const itemCenter = screenY + h / 2; 
 +    const distFromCenter = itemCenter - vc; 
 +    const absD = Math.abs(distFromCenter); 
 +    const maxD = wrapH * 0.52; 
 + 
 +    // 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, 1.4) * (item.type === 'sub' ? 52 : 68); 
 + 
 +    // fade near edges 
 +    const fadeFrac = Math.min(absD / (wrapH * 0.44), 1); 
 +    const opacity  = Math.max(0, 1 - Math.pow(fadeFrac, 1.8)); 
 + 
 +    if (opacity <= 0.01) { 
 +      if (existingEls[u]) existingEls[u].style.opacity = '0'; 
 +      return; 
 +    } 
 + 
 +    let el = existingEls[u]; 
 +    if (!el) { 
 +      el = document.createElement('div'); 
 +      el.dataset.uid = u; 
 +      el.innerHTML = buildInner(item); 
 +      el.addEventListener('click', () => onItemClick(item)); 
 +      carousel.appendChild(el); 
 +    } 
 + 
 +    el.className = 'ut-item'
 +      (item.type === 'sub' ? ' is-sub' : ' is-cat') + 
 +      ' ut-acc-' + item.cat.acc + 
 +      (isSel ? ' selected' : ''); 
 + 
 +    el.style.top     = screenY + 'px'; 
 +    el.style.height  = h + 'px'; 
 +    el.style.transform = `translateX(${push}px)`; 
 +    el.style.opacity = opacity.toFixed(3);
   });   });
 +
 +  carousel.style.height = wrapH + 'px';
 } }
  
-function selectCat(catId) { +function uid(item) { 
-  if (activeCatId === catId) { +  return item.type === 'cat' ? item.cat.id : item.cat.id + '__' + item.sub.id; 
-    // deselect +
-    activeCatId null; + 
-    activeSubId null+function buildInner(item) { 
-    showSplash();+  if (item.type === 'cat') { 
 +    return `<div class="ut-item-bg"></div> 
 +<div class="ut-item-overlay"></div> 
 +<div class="ut-item-accent-bar"></div> 
 +<div class="ut-item-inner"> 
 +  <i class="ti ${item.cat.icon} ut-item-icon" aria-hidden="true"></i> 
 +  <div class="ut-item-text"> 
 +    <div class="ut-item-label">${item.cat.label}</div> 
 +    <div class="ut-item-sub-count">${item.cat.subs.length} techniques</div> 
 +  </div> 
 +  <i class="ti ti-chevron-right" style="font-size:14px;color:#6B4FBB;flex-shrink:0" aria-hidden="true"></i> 
 +</div>`;
   } else {   } else {
-    activeCatId catId; +    return `<div class="ut-item-bg"></div> 
-    activeSubId null+<div class="ut-item-overlay"></div> 
-    showCat(catId);+<div class="ut-item-accent-bar"></div> 
 +<div class="ut-item-inner"> 
 +  <i class="ti ti-circle-dot ut-item-icon" aria-hidden="true"></i> 
 +  <div class="ut-item-text"> 
 +    <div class="ut-item-label">${item.sub.label}</div> 
 +  </div> 
 +  <i class="ti ti-arrow-right" style="font-size:13px;color:#6B4FBB;flex-shrink:0" aria-hidden="true"></i> 
 +</div>`;
   }   }
-  buildTiles(searchInput.value); 
-  scrollActiveTileIntoView(); 
 } }
  
-function selectSub(catId, subId) { +// ── CLICK ────────────────────────────────────────────── 
-  activeCatId = catId+function onItemClick(item) { 
-  activeSubId = subId+  if (item.type === 'cat') { 
-  showSub(catIdsubId); +    if (activeCatId === item.cat.id && activeSubId === null) { 
-  buildTiles(searchInput.value);+      activeCatId = null
 +      showSplash(); 
 +    } else { 
 +      activeCatId = item.cat.id; 
 +      activeSubId = null; 
 +      showCat(item.cat); 
 +    } 
 +  } else { 
 +    activeCatId = item.cat.id; 
 +    activeSubId = item.sub.id; 
 +    showSub(item.catitem.sub); 
 +  
 +  scrollToSelected(); 
 +  render();
 } }
 +
 +function scrollToSelected() {
 +  const items = buildItemList();
 +  const pos   = computePositions(items);
 +  const wrapH = document.getElementById('ut-carousel-wrap').offsetHeight;
 +  let selY = null, selH = CAT_H;
 +  items.forEach((item, i) => {
 +    const isSel = item.type === 'cat'
 +      ? activeCatId === item.cat.id && activeSubId === null
 +      : activeSubId === item.sub?.id;
 +    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  = document.getElementById('ut-splash');
 +const content = document.getElementById('ut-content');
  
 function showSplash() { function showSplash() {
Line 660: Line 673:
   content.classList.add('hidden');   content.classList.add('hidden');
 } }
- +function showCat(cat) {
-function showCat(catId) { +
-  const cat = CATS.find(c => c.id === catId);+
   splash.classList.add('hidden');   splash.classList.add('hidden');
   content.classList.remove('hidden');   content.classList.remove('hidden');
- +  document.getElementById('ut-breadcrumb').textContent    = 'Universal Tech';
-  document.getElementById('ut-breadcrumb').textContent = 'Universal Tech';+
   document.getElementById('ut-content-title').textContent = cat.label;   document.getElementById('ut-content-title').textContent = cat.label;
-  document.getElementById('ut-content-desc').textContent = cat.desc; +  document.getElementById('ut-content-desc').textContent  = cat.desc; 
- +  document.getElementById('ut-card-grid').innerHTML = ''; 
-  const grid = document.getElementById('ut-card-grid')+  document.getElementById('ut-open-btn').href = '/wiki/doku.php?id=' + cat.wikiId; 
-  grid.innerHTML = ''; +  document.getElementById('ut-btn-label').textContent = 'Open ' + cat.label + ' overview';
-  cat.subs.forEach(sub => { +
-    const card = document.createElement('a'); +
-    card.className = 'ut-guide-card'; +
-    card.href = `/wiki/doku.php?id=${sub.wikiId}`; +
-    card.innerHTML = `<div class="ut-guide-card-name">${sub.label}</div><div class="ut-guide-card-meta">${sub.desc}</div>`; +
-    card.addEventListener('click', (e) => { e.preventDefault(); selectSub(catId, sub.id); }); +
-    grid.appendChild(card); +
-  }); +
- +
-  const btn = document.getElementById('ut-open-btn')+
-  btn.href = `/wiki/doku.php?id=${cat.wikiId}`+
-  document.getElementById('ut-btn-label').textContent = `Open ${cat.labeloverview`;+
 } }
- +function showSub(catsub) {
-function showSub(catIdsubId) { +
-  const cat = CATS.find(c => c.id === catId); +
-  const sub = cat.subs.find(s => s.id === subId);+
   splash.classList.add('hidden');   splash.classList.add('hidden');
   content.classList.remove('hidden');   content.classList.remove('hidden');
- +  document.getElementById('ut-breadcrumb').textContent    'Universal Tech › ' + cat.label;
-  document.getElementById('ut-breadcrumb').textContent = `Universal Tech › ${cat.label}`;+
   document.getElementById('ut-content-title').textContent = sub.label;   document.getElementById('ut-content-title').textContent = sub.label;
-  document.getElementById('ut-content-desc').textContent = sub.desc;+  document.getElementById('ut-content-desc').textContent  = sub.desc; 
 +  document.getElementById('ut-card-grid').innerHTML = ''; 
 +  document.getElementById('ut-open-btn').href = '/wiki/doku.php?id=' + sub.wikiId; 
 +  document.getElementById('ut-btn-label').textContent = 'Open ' + sub.label + ' page'; 
 +}
  
-  const grid = document.getElementById('ut-card-grid'); +// ── SCROLL ───────────────────────────────────────────── 
-  grid.innerHTML ''; +const wrap = document.getElementById('ut-carousel-wrap'); 
-  ['Overview', 'Basic Setup', 'Intermediate', 'Advanced', 'Video Examples', 'Common Mistakes'].forEach(section => { +let momentumRunning false;
-    const card = document.createElement('a'); +
-    card.className = 'ut-guide-card'; +
-    card.href = `/wiki/doku.php?id=${sub.wikiId}`; +
-    card.innerHTML = `<div class="ut-guide-card-name">${section}</div><div class="ut-guide-card-meta">Guide section</div>`; +
-    grid.appendChild(card); +
-  });+
  
-  const btn document.getElementById('ut-open-btn'); +// Wheel sensitivity: low so small scrolls move little, fast scrolls build up 
-  btn.href = `/wiki/doku.php?id=${sub.wikiId}`; +const WHEEL_ACCEL   0.18  // fraction of deltaY added to velocity each event 
-  document.getElementById('ut-btn-label').textContent `Open ${sub.label} wiki page`+const FRICTION      0.82  // velocity multiplied each frame (lower = stops sooner) 
-}+const VEL_CAP       380   // max px/frame velocity 
 +const VEL_STOP      = 0.6;    // below this velocity, stop the loop
  
-function scrollActiveTileIntoView() { +function startMomentum() { 
-  setTimeout(() =+  if (momentumRunningreturn; 
-    const tiles tileList.querySelectorAll('.ut-cat-tile.active')+  momentumRunning true; 
-    if (tiles.length) tiles[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); +  function step() 
-  }, 50);+    velocity *FRICTION
 +    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(); 
-  '/wiki/lib/tpl/bootstrap3/videos/gameplay1.mp4'+  const scale e.deltaMode === 1 ? 20 : e.deltaMode === 2 ? 300 : 1; 
-  '/wiki/lib/tpl/bootstrap3/videos/gameplay2.mp4', +  const delta = e.deltaY * scale; 
-  '/wiki/lib/tpl/bootstrap3/videos/gameplay3.mp4', +  // Add a fraction of delta to velocity — proportional feel 
-  '/wiki/lib/tpl/bootstrap3/videos/gameplay4.mp4'+  velocity += delta * WHEEL_ACCEL; 
-]+  velocity = Math.max(-VEL_CAPMath.min(VEL_CAP, velocity)); 
-let vidIdx 0+  startMomentum(); 
-video.addEventListener('ended', () => { +}, { passive: false }); 
-  vidIdx = (vidIdx + 1% sources.length+ 
-  video.src sources[vidIdx]+// DRAG 
-  video.play();+wrap.addEventListener('mousedown', e => { 
 +  momentumRunning = false; 
 +  velocity = 0; 
 +  isDragging = true; lastDragY = e.clientY; lastDragT = performance.now(); 
 +  wrap.style.cursor = 'grabbing'; 
 +  e.preventDefault(); 
 +}); 
 +window.addEventListener('mousemove', e => { 
 +  if (!isDragging) return; 
 +  const now = performance.now(); 
 +  const dy  = e.clientY - lastDragY; 
 +  const dt  = Math.max(now - lastDragT1); 
 +  velocity  = (-dy / dt) * 8
 +  scrollY  -dy; 
 +  lastDragY = e.clientY; lastDragT = now; 
 +  render(); 
 +})
 +window.addEventListener('mouseup', () => { 
 +  if (!isDraggingreturn
 +  isDragging false
 +  wrap.style.cursor = 'grab'; 
 +  startMomentum();
 }); });
  
-searchInput.addEventListener('input', () => buildTiles(searchInput.value));+// TOUCH 
 +wrap.addEventListener('touchstart', e => { 
 +  momentumRunning = false; velocity = 0; 
 +  isDragging = true; lastDragY = e.touches[0].clientY; lastDragT = performance.now()
 +}, { passive: true }); 
 +wrap.addEventListener('touchmove',=> 
 +  if (!isDragging) return; 
 +  const now = performance.now(); 
 +  const dy  = e.touches[0].clientY - lastDragY; 
 +  velocity  = (-dy / Math.max(now - lastDragT, 1)) * 8; 
 +  scrollY  -= dy; 
 +  lastDragY = e.touches[0].clientY; lastDragT = now; 
 +  render(); 
 +}, { passive: true }); 
 +wrap.addEventListener('touchend', () => { isDragging = false; startMomentum(); }); 
 + 
 +// ── SEARCH ───────────────────────────────────────────── 
 +document.getElementById('ut-search').addEventListener('input', e => { 
 +  filter = e.target.value
 +  render()
 +});
  
-// Initial render +// ── INIT ─────────────────────────────────────────────── 
-buildTiles();+window.addEventListener('resize', render); 
 +// start scroll so first item is near center 
 +scrollY = -(document.getElementById('ut-carousel-wrap').offsetHeight / 2 - CAT_H / 2); 
 +render();
  
 })(); })();
  • universaltech/start.1782108804.txt.gz
  • Last modified: 2026/06/22 06:13
  • by jumpstartadmin