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:19] jumpstartadminuniversaltech:start [2026/06/22 06:57] (current) jumpstartadmin
Line 7: Line 7:
 <style> <style>
 body.mode_show #dokuwiki__site { display: none !important; } body.mode_show #dokuwiki__site { display: none !important; }
- 
 * { box-sizing: border-box; margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; }
- 
 .navbar.navbar-default { z-index: 10000 !important; } .navbar.navbar-default { z-index: 10000 !important; }
- 
 body:not(.mode_show) #ut-page { display: none !important; } body:not(.mode_show) #ut-page { display: none !important; }
  
 #ut-page { #ut-page {
   position: fixed;   position: fixed;
-  top: 50px+  top: 0
-  left: 0; +  left: 0; right: 0; bottom: 0;
-  right: 0; +
-  bottom: 0;+
   z-index: 9000;   z-index: 9000;
   background: #0A0816;   background: #0A0816;
Line 35: 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.3;+  opacity: 0.55;
 } }
 #ut-video-bg::after { #ut-video-bg::after {
Line 44: Line 38:
   position: absolute;   position: absolute;
   inset: 0;   inset: 0;
-  background: linear-gradient(to right, rgba(10,8,22,0.95) 25%, rgba(10,8,22,0.560%, rgba(10,8,22,0.15) 100%);+  background: linear-gradient(to right, rgba(10,8,22,0.95) 20%, rgba(10,8,22,0.5555%, rgba(10,8,22,0.1) 100%);
 } }
  
-/* LEFT — content area */+/* LEFT */
 #ut-left { #ut-left {
   position: relative;   position: relative;
Line 91: Line 85:
   max-width: 400px;   max-width: 400px;
   line-height: 1.65;   line-height: 1.65;
-  margin-bottom: 30px; 
-} 
-#ut-splash-pills { 
-  display: flex; 
-  flex-wrap: wrap; 
-  gap: 8px; 
-  pointer-events: all; 
-} 
-.ut-pill { 
-  background: rgba(61,43,122,0.35); 
-  border: 1px solid rgba(108,79,187,0.4); 
-  border-radius: 20px; 
-  padding: 6px 16px; 
-  font-size: 12px; 
-  color: #B8ADDB; 
-  cursor: pointer; 
-  font-family: 'Forevs', sans-serif; 
-  transition: all 0.15s; 
-} 
-.ut-pill:hover { 
-  background: rgba(108,79,187,0.45); 
-  color: #EDE8F5; 
-  border-color: rgba(184,173,219,0.55); 
 } }
  
Line 186: Line 157:
 } }
  
-/* RIGHT — osu-style carousel */+/* RIGHT — osu carousel */
 #ut-carousel-wrap { #ut-carousel-wrap {
   position: relative;   position: relative;
   z-index: 10;   z-index: 10;
-  width: 440px;+  width: 500px;
   flex-shrink: 0;   flex-shrink: 0;
   overflow: hidden;   overflow: hidden;
   display: flex;   display: flex;
   align-items: center;   align-items: center;
-  cursor: grab; 
   user-select: none;   user-select: none;
 } }
-#ut-carousel-wrap:active { cursor: grabbing; } 
  
 #ut-carousel { #ut-carousel {
Line 204: Line 173:
   right: 0;   right: 0;
   width: 100%;   width: 100%;
-  /* items positioned absolutely within this */ 
 } }
  
-/* Each item in the carousel */+/* tiles */
 .ut-item { .ut-item {
   position: absolute;   position: absolute;
   right: 0;   right: 0;
-  height: 64px; +  border-radius: 16px 0 0 16px;
-  border-radius: 14px 0 0 14px; +
-  display: flex; +
-  align-items: center; +
-  gap: 0;+
   overflow: hidden;   overflow: hidden;
   cursor: pointer;   cursor: pointer;
-  transition: 
-    width 0.28s cubic-bezier(0.4,0,0.2,1), 
-    transform 0.28s cubic-bezier(0.4,0,0.2,1), 
-    height 0.28s cubic-bezier(0.4,0,0.2,1), 
-    opacity 0.28s; 
   border: 1px solid rgba(108,79,187,0.2);   border: 1px solid rgba(108,79,187,0.2);
   border-right: none;   border-right: none;
-  will-change: transform, width;+  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: 340px; } +.ut-item.is-cat        { width: 420px; height: 80px; } 
-.ut-item.is-sub { width: 300px; }+.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.selected { height: 80px; } 
-.ut-item.is-cat.selected { width: 400px; } 
-.ut-item.is-sub.selected { width: 360px; } 
- 
-/* accent bg */ 
 .ut-item-bg { .ut-item-bg {
-  position: absolute; +  position: absolute; inset: 0; 
-  inset: 0; +  opacity: 0.2;
-  opacity: 0.22;+
   transition: opacity 0.2s;   transition: opacity 0.2s;
 } }
-.ut-item:hover .ut-item-bg { opacity: 0.32; }+.ut-item:hover .ut-item-bg { opacity: 0.3; }
 .ut-item.selected .ut-item-bg { opacity: 0.38; } .ut-item.selected .ut-item-bg { opacity: 0.38; }
  
 .ut-item-overlay { .ut-item-overlay {
-  position: absolute; +  position: absolute; inset: 0; 
-  inset: 0; +  background: linear-gradient(90deg, rgba(10,8,22,0.85) 0%, rgba(10,8,22,0.25) 100%);
-  background: linear-gradient(90deg, rgba(10,8,22,0.82) 0%, rgba(10,8,22,0.3) 100%);+
 } }
  
Line 256: Line 214:
   display: flex;   display: flex;
   align-items: center;   align-items: center;
-  gap: 14px+  gap: 16px
-  padding: 0 20px;+  padding: 0 22px;
   width: 100%;   width: 100%;
   height: 100%;   height: 100%;
Line 263: Line 221:
  
 .ut-item-icon { .ut-item-icon {
-  font-size: 20px;+  font-size: 22px;
   color: #9D85DB;   color: #9D85DB;
   flex-shrink: 0;   flex-shrink: 0;
   transition: color 0.2s, font-size 0.2s;   transition: color 0.2s, font-size 0.2s;
 } }
-.ut-item.selected .ut-item-icon { color: #C8BFED; font-size: 22px; } +.ut-item.selected .ut-item-icon  { color: #C8BFED; font-size: 24px; } 
-.ut-item.is-sub .ut-item-icon { font-size: 14px; color: #6B4FBB; } +.ut-item.is-sub .ut-item-icon    { font-size: 15px; color: #6B4FBB; } 
-.ut-item.is-sub.selected .ut-item-icon { font-size: 16px; color: #9D85DB; }+.ut-item.is-sub.selected .ut-item-icon { font-size: 17px; color: #9D85DB; }
  
 .ut-item-text { flex: 1; min-width: 0; } .ut-item-text { flex: 1; min-width: 0; }
 .ut-item-label { .ut-item-label {
-  font-size: 14px;+  font-size: 15px;
   font-weight: 600;   font-weight: 600;
   color: #C8BFED;   color: #C8BFED;
Line 282: Line 240:
   transition: color 0.2s, font-size 0.2s;   transition: color 0.2s, font-size 0.2s;
 } }
-.ut-item.selected .ut-item-label { color: #EDE8F5; font-size: 16px; } +.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 .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.is-sub.selected .ut-item-label { color: #C8BFED; font-size: 14px; }
  
Line 289: Line 247:
   font-size: 11px;   font-size: 11px;
   color: #6B4FBB;   color: #6B4FBB;
-  margin-top: 2px; +  margin-top: 3px;
-  transition: opacity 0.2s;+
 } }
  
 .ut-item-accent-bar { .ut-item-accent-bar {
   position: absolute;   position: absolute;
-  left: 0; +  left: 0; top: 0; bottom: 0;
-  top: 0; +
-  bottom: 0;+
   width: 3px;   width: 3px;
-  border-radius: 14px 0 0 14px;+  border-radius: 16px 0 0 16px;
   opacity: 0;   opacity: 0;
   transition: opacity 0.2s;   transition: opacity 0.2s;
Line 306: Line 261:
  
 /* accent colors */ /* accent colors */
-.ut-acc-movement .ut-item-bg { background: linear-gradient(90deg,#3D2B7A,#6B4FBB); }+.ut-acc-movement .ut-item-bg  { background: linear-gradient(90deg,#3D2B7A,#6B4FBB); }
 .ut-acc-movement .ut-item-accent-bar { background: #9D85DB; } .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-bg    { background: linear-gradient(90deg,#7A2B4A,#BB4F7A);
-.ut-acc-combat .ut-item-accent-bar { background: #DB85AD; } +.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-bg     { background: linear-gradient(90deg,#1E5A7A,#2B8FBB);
-.ut-acc-items .ut-item-accent-bar { background: #85CADB; } +.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-bg       { background: linear-gradient(90deg,#1E7A4A,#2BBB6F);
-.ut-acc-map .ut-item-accent-bar { background: #85DBA8; }+.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-bg { background: linear-gradient(90deg,#7A5A1E,#BB8F2B); }
-.ut-acc-mechanics .ut-item-accent-bar { background: #DBC085; }+.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-bg { background: linear-gradient(90deg,#5A1E7A,#8F2BBB); }
-.ut-acc-abilities .ut-item-accent-bar { background: #C085DB; }+.ut-acc-abilities .ut-item-accent-bar{ background: #C085DB; }
  
-/* search overlay at top of carousel */+/* search */
 #ut-search-wrap { #ut-search-wrap {
   position: absolute;   position: absolute;
-  top: 28px; +  top: 24px; right: 0; 
-  right: 0; +  width: 440px;
-  width: 380px;+
   z-index: 20;   z-index: 20;
-  padding-right: 0; 
 } }
 #ut-search { #ut-search {
   width: 100%;   width: 100%;
-  background: rgba(10,8,22,0.75);+  background: rgba(10,8,22,0.8);
   border: 1px solid rgba(108,79,187,0.4);   border: 1px solid rgba(108,79,187,0.4);
   border-right: none;   border-right: none;
Line 350: Line 303:
   <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">
     </video>     </video>
   </div>   </div>
  
-  <!-- LEFT CONTENT --> 
   <div id="ut-left">   <div id="ut-left">
     <div id="ut-splash">     <div id="ut-splash">
Line 360: Line 312:
       <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">Mechanics and techniques that apply to every hero in Deadlock. 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"></div> 
     </div>     </div>
     <div id="ut-content" class="hidden">     <div id="ut-content" class="hidden">
Line 371: Line 322:
   </div>   </div>
  
-  <!-- RIGHT CAROUSEL --> 
   <div id="ut-carousel-wrap">   <div id="ut-carousel-wrap">
     <div id="ut-search-wrap">     <div id="ut-search-wrap">
Line 380: Line 330:
  
 </div> </div>
- 
 <script> <script>
 (function(){ (function(){
Line 386: Line 335:
 const CATS = [ const CATS = [
   { id:'movement', icon:'ti-run', label:'Movement', acc:'movement',   { id:'movement', icon:'ti-run', label:'Movement', acc:'movement',
-    desc:'Core locomotion mechanics — how you movefly, and traverse the map at speed.',+    desc:'Core ground movement — walkingsprinting, crouching, sliding, jumping, and stamina management.',
     wikiId:'universaltech:movement',     wikiId:'universaltech:movement',
     subs:[     subs:[
-      {id:'bunnyhop',   label:'Bunny Hop',    desc:'Speed maintenance between jumps.',              wikiId:'universaltech:movement:bunnyhop'}, +      {id:'walking',   label:'Walking',   desc:'Base movement speed and mechanics.', wikiId:'universaltech:movement:walking'}, 
-      {id:'airstrafing',label:'Air Strafing', desc:'Directional control while airborne.',           wikiId:'universaltech:movement:airstrafing'}, +      {id:'sprinting', label:'Sprinting', desc:'Sprint mechanics and speed thresholds.', wikiId:'universaltech:movement:sprinting'}, 
-      {id:'walljump',   label:'Wall Jump',    desc:'Bouncing off geometry for height or distance.', wikiId:'universaltech:movement:walljump'}, +      {id:'crouching', label:'Crouching', desc:'Crouch movement and hitbox changes.', wikiId:'universaltech:movement:crouching'}, 
-      {id:'slidehop',   label:'Slide Hop',    desc:'Chaining slides and hops for momentum.',        wikiId:'universaltech:movement:slidehop'}, +      {id:'sliding',   label:'Sliding',   desc:'Slide mechanics, speed, and timing.', wikiId:'universaltech:movement:sliding'}, 
-      {id:'zipline',    label:'Zipline Tech', desc:'Advanced entryexit, and speed tricks.',       wikiId:'universaltech:movement:zipline'},+      {id:'stamina',   label:'Stamina',   desc:'Stamina system, regeneration, and management.', wikiId:'universaltech:movement:stamina'}, 
 +      {id:'jumping',   label:'Jumping',   desc:'Jump mechanics, height, 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.', 
 +    wikiId:'universaltech:dashing', 
 +    subs:[ 
 +      {id:'strafedash', label:'Strafe Dashing',         desc:'Dashing at angles for lateral speed.', wikiId:'universaltech:dashing:strafedash'}, 
 +      {id:'dashjump',   label:'Dash Jumping',           desc:'Combining a dash into a jump for distance.', wikiId:'universaltech:dashing:dashjump'}, 
 +      {id:'dashslide',  label:'Dash Sliding',           desc:'Cancelling a dash into a slide.', wikiId:'universaltech:dashing:dashslide'}, 
 +      {id:'iad',        label:'Instant Air Dash (I.A.D)',desc:'Minimal-height air dash for fast horizontal movement.', wikiId:'universaltech:dashing:iad'}, 
 +      {id:'wallstrafe', label:'Wall Strafe',            desc:'Dashing along walls for momentum.', wikiId:'universaltech:dashing:wallstrafe'}, 
 +    ]}, 
 +  { 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', 
 +    subs:[ 
 +      {id:'mantleslide',  label:'Mantle Slide',      desc:'Sliding immediately out of a mantle.', wikiId:'universaltech:mantling:mantleslide'}, 
 +      {id:'superglide',   label:'Super Glide',       desc:'Gaining extra airtime off a mantle.', wikiId:'universaltech:mantling:superglide'}, 
 +      {id:'glidewj',      label:'Glide Wall Jump',   desc:'Wall jumping during a glide for height.', wikiId:'universaltech:mantling:glidewj'}, 
 +      {id:'mantlestrafe', label:'Mantle Strafe',     desc:'Angling movement during mantle animation.', wikiId:'universaltech:mantling:mantlestrafe'}, 
 +      {id:'fakemantle',   label:'Fake Mantle',       desc:'Cancelling the mantle animation early.', wikiId:'universaltech:mantling:fakemantle'}, 
 +      {id:'mantlelaunch', label:'Mantle Launch',     desc:'Using mantle momentum to launch far.', wikiId:'universaltech:mantling:mantlelaunch'},
     ]},     ]},
-  { id:'combat', icon:'ti-crosshair', label:'Combat', acc:'combat', +  { id:'ziplines', icon:'ti-line-dashed', label:'Ziplines', acc:'items', 
-    desc:'Engagement techniques — positioningtimingburst windows, and aim mechanics.', +    desc:'Zipline techs — momentum conservationcancelsjumps, and launch techniques.', 
-    wikiId:'universaltech:combat',+    wikiId:'universaltech:ziplines',
     subs:[     subs:[
-      {id:'peekshoot',   label:'Peek Shooting',  desc:'Minimize exposure while landing shots.',      wikiId:'universaltech:combat:peekshoot'}, +      {id:'zmc',     label:'Zipline Momentum Conservation (Z.M.C)', desc:'Keeping speed when leaving a zipline.', wikiId:'universaltech:ziplines:zmc'}, 
-      {id:'jumpshoot',   label:'Jump Shooting',  desc:'Accuracy and timing when airborne.',          wikiId:'universaltech:combat:jumpshoot'}, +      {id:'zipcancel',label:'Zip Cancel', desc:'Cancelling off a zipline at the right moment.', wikiId:'universaltech:ziplines:zipcancel'}, 
-      {id:'slideshoot',  label:'Slide Shooting', desc:'Aim control through slide animations.',       wikiId:'universaltech:combat:slideshoot'}, +      {id:'zipjump', label:'Zip Jump',   desc:'Jumping from a zipline with extra height.', wikiId:'universaltech:ziplines:zipjump'}, 
-      {id:'reloadcancel',label:'Reload Cancel',  desc:'Safely cutting reload animations short.',    wikiId:'universaltech:combat:reloadcancel'},+      {id:'zipdash', label:'Zip Dash',   desc:'Dashing on or off a zipline.', wikiId:'universaltech:ziplines:zipdash'}, 
 +      {id:'ziplaunch',label:'Zip Launch',desc:'Using zipline exit for maximum launch speed.', wikiId:'universaltech:ziplines:ziplaunch'},
     ]},     ]},
-  { id:'items', icon:'ti-package', label:'Item Tech', acc:'items', +  { id:'ropes', icon:'ti-minus-vertical', label:'Ropes', acc:'mechanics', 
-    desc:'Hidden mechanics and advanced interactions with universal items.', +    desc:'Rope techs — cancels, launches, and wall jumps off rope anchors.', 
-    wikiId:'universaltech:items',+    wikiId:'universaltech:ropes',
     subs:[     subs:[
-      {id:'warpstone',  label:'Warp Stone',    desc:'Positioning tricks and cancel windows.',  wikiId:'universaltech:items:warpstone'}, +      {id:'ropecancel', label:'Rope Cancel',    desc:'Cancelling rope grab for momentum burst.', wikiId:'universaltech:ropes:ropecancel'}, 
-      {id:'coldfront',  label:'Cold Front',    desc:'Slow tech and zone control setups.',      wikiId:'universaltech:items:coldfront'}, +      {id:'ropelaunch', label:'Rope Launch',    desc:'Using rope to catapult in a direction.', wikiId:'universaltech:ropes:ropelaunch'}, 
-      {id:'magiccarpet',label:'Magic Carpet',  desc:'Hover positioning and edge-case uses.',   wikiId:'universaltech:items:magiccarpet'}, +      {id:'ropewj',     label:'Rope Wall Jump', desc:'Wall jumping off a rope attachment point.', wikiId:'universaltech:ropes:ropewj'},
-      {id:'cloakmantle',label:'Cloak + Mantle',desc:'Invisibility with mobility combos.',      wikiId:'universaltech:items:cloakmantle'},+
     ]},     ]},
-  { id:'map', icon:'ti-map', label:'Map Tech', acc:'map', +  { id:'vent', icon:'ti-wind', label:'Vent', acc:'abilities', 
-    desc:'Geometry exploits, map-specific routes, and positional advantages.', +    desc:'Vent interactions — boost techs and stun mechanics.', 
-    wikiId:'universaltech:map',+    wikiId:'universaltech:vent',
     subs:[     subs:[
-      {id:'roofjumps',label:'Roof Jumps',         desc:'Common roof access points across all lanes.',    wikiId:'universaltech:map:roofjumps'}, +      {id:'ventboost', label:'Vent Boost', desc:'Using vents for extra upward velocity.', wikiId:'universaltech:vent:ventboost'}, 
-      {id:'shortcuts',label:'Shortcuts',           desc:'Fast-path routes used in high-level play.',     wikiId:'universaltech:map:shortcuts'}, +      {id:'ventstun',  label:'Vent Stun',  desc:'Vent stun timing and counterplay.', wikiId:'universaltech:vent:ventstun'},
-      {id:'oob',      label:'Out-of-Bounds Tech',  desc:'Legal geometry clips and unintended spots.',    wikiId:'universaltech:map:oob'},+
     ]},     ]},
-  { id:'mechanics', icon:'ti-settings', label:'Game Mechanics', acc:'mechanics', +  { id:'melee', icon:'ti-sword', label:'Melee', acc:'combat', 
-    desc:'Engine and system-level behaviour that affects every hero equally.', +    desc:'Melee techs — light, heavy, parry, cancels, and advanced melee interactions.', 
-    wikiId:'universaltech:mechanics',+    wikiId:'universaltech:melee',
     subs:[     subs:[
-      {id:'animation',label:'Animation Cancels',    desc:'Breaking animations to act faster.',          wikiId:'universaltech:mechanics:animation'}, +      {id:'lightmelee', label:'Light Melee',               desc:'Light melee timing and combo use.', wikiId:'universaltech:melee:lightmelee'}, 
-      {id:'momentum', label:'Momentum Preservation',desc:'How the engine stores and bleeds speed.',     wikiId:'universaltech:mechanics:momentum'}, +      {id:'heavymelee', label:'Heavy Melee',               desc:'Heavy melee range, knockback, and setup.', wikiId:'universaltech:melee:heavymelee'}, 
-      {id:'phasing',  label:'Phasing Windows',      desc:'Invincibility and i-frame timing.',           wikiId:'universaltech:mechanics:phasing'},+      {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:'abilities', icon:'ti-bolt', label:'Ability Tech', acc:'abilities', +  { id:'walljump', icon:'ti-arrow-bounce', label:'Wall Jumping', acc:'movement', 
-    desc:'Universal interactions between abilitiesitems, and game systems.', +    desc:'Wall jump techs — directional control, corner and edge boostssurf, and fatigue management.', 
-    wikiId:'universaltech:abilities',+    wikiId:'universaltech:walljump',
     subs:[     subs:[
-      {id:'quickcast', label:'Quick Cast',   desc:'Instant ability activation setups.',     wikiId:'universaltech:abilities:quickcast'}, +      {id:'wjd',       label:'Wall Jump Direction (W.J.D)', desc:'Controlling angle and direction off walls.', wikiId:'universaltech:walljump:wjd'}, 
-      {id:'buffstack', label:'Buff Stacking',desc:'Layering buffs for maximum burst.',      wikiId:'universaltech:abilities:buffstack'}, +      {id:'cornerboost',label:'Corner Boost',              desc:'Using corners for extra launch speed.', wikiId:'universaltech:walljump:cornerboost'}, 
-      {id:'canceltech',label:'Cancel Tech',  desc:'Cancelling abilities for free casts.',   wikiId:'universaltech:abilities:canceltech'},+      {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 ────────────────────────────────────────────── // ── STATE ──────────────────────────────────────────────
Line 443: Line 453:
 let activeSubId = null; let activeSubId = null;
 let filter = ''; let filter = '';
 +
 +// ── LAYOUT CONSTANTS ────────────────────────────────────
 +const CAT_H     = 80;
 +const CAT_H_SEL = 96;
 +const SUB_H     = 60;
 +const SUB_H_SEL = 72;
 +const GAP       = 7;
 +
 +// scrollY = offset into virtual list (px). Center of viewport = scrollY.
 +let scrollY   = 0;
 +let velocity  = 0;
 +let rafId     = null;
 +let isDragging = false;
 +let lastDragY  = 0;
 +let lastDragT  = 0;
  
 // ── FLAT ITEM LIST ────────────────────────────────────── // ── FLAT ITEM LIST ──────────────────────────────────────
-// Builds the ordered list of items to render in the carousel 
 function buildItemList() { function buildItemList() {
   const items = [];   const items = [];
   const q = filter.toLowerCase();   const q = filter.toLowerCase();
   CATS.forEach(cat => {   CATS.forEach(cat => {
-    const subMatch = cat.subs.filter(s => !q || s.label.toLowerCase().includes(q) || cat.label.toLowerCase().includes(q));+    const subMatch = cat.subs.filter(s => 
 +      !q || s.label.toLowerCase().includes(q) || cat.label.toLowerCase().includes(q) 
 +    );
     if (q && subMatch.length === 0 && !cat.label.toLowerCase().includes(q)) return;     if (q && subMatch.length === 0 && !cat.label.toLowerCase().includes(q)) return;
     items.push({ type:'cat', cat, sub:null });     items.push({ type:'cat', cat, sub:null });
     if (activeCatId === cat.id) {     if (activeCatId === cat.id) {
-      const subs = q ? subMatch : cat.subs+      (q ? subMatch : cat.subs).forEach(sub => items.push({ type:'sub', cat, sub }));
-      subs.forEach(sub => items.push({ type:'sub', cat, sub }));+
     }     }
   });   });
Line 461: Line 486:
 } }
  
-// ── LAYOUT CONSTANTS ──────────────────────────────────── +// ── POSITIONS ─────────────────────────────────────────── 
-const ITEM_H 64+function computePositions(items) { 
-const ITEM_H_SEL 80; +  let y 0
-const GAP 6; +  return items.map(item => { 
-const INDENT_SUB 24// subs indented slightly +    const isSel item.type === 'cat' 
-const CAROUSEL_AREA_H () => document.getElementById('ut-carousel-wrap').offsetHeight; +      ? activeCatId === item.cat.id && activeSubId === null 
- +      : activeSubId === item.sub.id
-// scroll offset (px from top of virtual list to viewport top+    const item.type === 'cat? (isSel ? CAT_H_SEL : CAT_H(isSel ? SUB_H_SEL : SUB_H); 
-let scrollY 0+    const pos { y, h, isSel }
-let velocity 0+    y +h + GAP
-let rafId = null;+    return pos; 
 +  }); 
 +}
  
 // ── RENDER ───────────────────────────────────────────── // ── RENDER ─────────────────────────────────────────────
 function render() { function render() {
   const carousel = document.getElementById('ut-carousel');   const carousel = document.getElementById('ut-carousel');
-  const items = buildItemList(); +  const wrapH    = document.getElementById('ut-carousel-wrap').offsetHeight; 
-  const wrapH CAROUSEL_AREA_H();+  const items    = buildItemList(); 
 +  const pos      computePositions(items)
 +  const totalH   = pos.length ? pos[pos.length-1].y + pos[pos.length-1].h : 0;
  
-  // compute y positions +  // clamp scroll: allow scrolling through the whole list 
-  const positions = []; +  const minScroll = -(wrapH / 2)
-  let y = 0; +  const maxScroll = totalH - wrapH / 2;
-  items.forEach((item, i) => { +
-    const isSel = item.type==='cat' +
-      ? activeCatId===item.cat.id && activeSubId===null +
-      : activeSubId===item.sub.id; +
-    const h = isSel ? ITEM_H_SEL : ITEM_H; +
-    positions.push({ y, h, isSel }); +
-    y += h + GAP; +
-  }); +
-  const totalH = y; +
- +
-  // clamp scroll +
-  const minScroll = 0+
-  const maxScroll = Math.max(0, totalH - wrapH + 120);+
   scrollY = Math.max(minScroll, Math.min(maxScroll, scrollY));   scrollY = Math.max(minScroll, Math.min(maxScroll, scrollY));
  
-  // center selected item if exists +  // track which uids exist
-  // (auto-scroll is done on select, not on every render) +
- +
-  // remove old els that no longer match+
   const existingEls = {};   const existingEls = {};
-  carousel.querySelectorAll('.ut-item').forEach(el => { +  carousel.querySelectorAll('.ut-item').forEach(el => { existingEls[el.dataset.uid] = el; });
-    existingEls[el.dataset.uid] = el; +
-  }); +
   const newUids = new Set();   const newUids = new Set();
-  items.forEach((item, i) => +  items.forEach(item => newUids.add(uid(item))); 
-    const uid = item.type==='cat' ? item.cat.id : item.cat.id+'__'+item.sub.id; +  Object.keys(existingEls).forEach(u => { if (!newUids.has(u)) existingEls[u].remove(); });
-    newUids.add(uid); +
-  });+
  
-  // remove stale +  // viewport center 
-  Object.keys(existingEls).forEach(uid => { +  const vc wrapH / 2;
-    if (!newUids.has(uid)) existingEls[uid].remove(); +
-  });+
  
-  // upsert items 
   items.forEach((item, i) => {   items.forEach((item, i) => {
-    const uid = item.type==='cat' ? item.cat.id : item.cat.id+'__'+item.sub.id+    const uid(item)
-    const { y: iy, h, isSel } = positions[i]+    const { y: iy, h, isSel } = pos[i];
-    const screenY = iy - scrollY + wrapH/2 - totalH/2;+
  
-    // osu-style: items further from center pushed right +    // position relative to viewport center 
-    const centerY wrapH / 2+    const screenY iy - scrollY
-    const itemCenterY = screenY + h/2; +    const itemCenter = screenY + h / 2; 
-    const dist = Math.abs(itemCenterY - centerY); +    const distFromCenter = itemCenter - vc; 
-    const maxDist = wrapH / 2+    const absD = Math.abs(distFromCenter); 
-    const pushRight = Math.min(dist maxDist, 1) * (item.type==='sub'40 55);+    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[uid];+    let el = existingEls[u];
     if (!el) {     if (!el) {
       el = document.createElement('div');       el = document.createElement('div');
-      el.className = 'ut-item'; +      el.dataset.uid = u
-      el.dataset.uid = uid+      el.innerHTML = buildInner(item);
-      el.innerHTML = buildItemHTML(item);+
       el.addEventListener('click', () => onItemClick(item));       el.addEventListener('click', () => onItemClick(item));
       carousel.appendChild(el);       carousel.appendChild(el);
     }     }
  
-    // update classes 
     el.className = 'ut-item' +     el.className = 'ut-item' +
-      (item.type==='sub' ? ' is-sub' : ' is-cat') + +      (item.type === 'sub' ? ' is-sub' : ' is-cat') + 
-      (' ut-acc-'+item.cat.acc++      ' ut-acc-' + item.cat.acc +
       (isSel ? ' selected' : '');       (isSel ? ' selected' : '');
  
-    el.style.top = screenY + 'px'; +    el.style.top     = screenY + 'px'; 
-    el.style.height = h + 'px'; +    el.style.height  = h + 'px'; 
-    el.style.transform = `translateX(${pushRight}px)`; +    el.style.transform = `translateX(${push}px)`; 
-    el.style.opacity = dist > wrapH * 0.85 ? '0' : dist > wrapH * 0.65 ? '0.4' : '1';+    el.style.opacity = opacity.toFixed(3);
   });   });
  
-  // set carousel height so scroll math works 
   carousel.style.height = wrapH + 'px';   carousel.style.height = wrapH + 'px';
 } }
  
-function buildItemHTML(item) {+function uid(item) { 
 +  return item.type === 'cat' ? item.cat.id : item.cat.id + '__' + item.sub.id; 
 +
 + 
 +function buildInner(item) {
   if (item.type === 'cat') {   if (item.type === 'cat') {
-    return ` +    return `<div class="ut-item-bg"></div> 
-      <div class="ut-item-bg"></div> +<div class="ut-item-overlay"></div> 
-      <div class="ut-item-overlay"></div> +<div class="ut-item-accent-bar"></div> 
-      <div class="ut-item-accent-bar"></div> +<div class="ut-item-inner"> 
-      <div class="ut-item-inner"> +  <i class="ti ${item.cat.icon} ut-item-icon" aria-hidden="true"></i> 
-        <i class="ti ${item.cat.icon} ut-item-icon" aria-hidden="true"></i> +  <div class="ut-item-text"> 
-        <div class="ut-item-text"> +    <div class="ut-item-label">${item.cat.label}</div> 
-          <div class="ut-item-label">${item.cat.label}</div> +    <div class="ut-item-sub-count">${item.cat.subs.length} techniques</div> 
-          <div class="ut-item-sub-count">${item.cat.subs.length} techniques</div> +  </div> 
-        </div> +  <i class="ti ti-chevron-right" style="font-size:14px;color:#6B4FBB;flex-shrink:0" aria-hidden="true"></i> 
-        <i class="ti ti-chevron-right" style="font-size:13px;color:#6B4FBB;flex-shrink:0" aria-hidden="true"></i> +</div>`;
-      </div>`;+
   } else {   } else {
-    return ` +    return `<div class="ut-item-bg"></div> 
-      <div class="ut-item-bg"></div> +<div class="ut-item-overlay"></div> 
-      <div class="ut-item-overlay"></div> +<div class="ut-item-accent-bar"></div> 
-      <div class="ut-item-accent-bar"></div> +<div class="ut-item-inner"> 
-      <div class="ut-item-inner"> +  <i class="ti ti-circle-dot ut-item-icon" aria-hidden="true"></i> 
-        <i class="ti ti-circle-dot ut-item-icon" aria-hidden="true"></i> +  <div class="ut-item-text"> 
-        <div class="ut-item-text"> +    <div class="ut-item-label">${item.sub.label}</div> 
-          <div class="ut-item-label">${item.sub.label}</div> +  </div> 
-        </div> +  <i class="ti ti-arrow-right" style="font-size:13px;color:#6B4FBB;flex-shrink:0" aria-hidden="true"></i> 
-        <i class="ti ti-arrow-right" style="font-size:12px;color:#6B4FBB;flex-shrink:0" aria-hidden="true"></i> +</div>`;
-      </div>`;+
   }   }
 } }
  
-// ── CLICK HANDLERS ─────────────────────────────────────+// ── CLICK ──────────────────────────────────────────────
 function onItemClick(item) { function onItemClick(item) {
   if (item.type === 'cat') {   if (item.type === 'cat') {
Line 606: Line 623:
  
 function scrollToSelected() { function scrollToSelected() {
-  // recalc positions after state change 
   const items = buildItemList();   const items = buildItemList();
-  const wrapH CAROUSEL_AREA_H(); +  const pos   computePositions(items); 
-  let y 0+  const wrapH document.getElementById('ut-carousel-wrap').offsetHeight
-  let selY = null, selH = ITEM_H+  let selY = null, selH = CAT_H
-  items.forEach(item => { +  items.forEach((item, i) => { 
-    const isSel = item.type==='cat' +    const isSel = item.type === 'cat' 
-      ? activeCatId===item.cat.id && activeSubId===null +      ? activeCatId === item.cat.id && activeSubId === null 
-      : activeSubId===item.sub?.id+      : activeSubId === item.sub?.id; 
-    const h = isSel ? ITEM_H_SEL : ITEM_H+    if (isSel) { selY = pos[i].y; selH = pos[i].h; }
-    if (isSel) { selY = y; selH = h; } +
-    y += h + GAP;+
   });   });
-  const totalH = y; 
   if (selY !== null) {   if (selY !== null) {
-    // target: selected item centered +    animateScroll(selY + selH / 2 - wrapH / 2);
-    const target = selY + selH/2 - totalH/2+
-    animateScrollTo(target);+
   }   }
 } }
  
-function animateScrollTo(target) { +// Spring scroll — sets velocity toward target naturally instead of teleporting 
-  const start scrollY+function animateScroll(target) { 
-  const dist target - start+  // Stop any current momentum and spring toward target 
-  const dur 380+  momentumRunning false
-  const t0 performance.now()+  velocity 0
-  function step(now) { +  const SPRING 0.12 // attraction strength per frame 
-    const p = Math.min((now - t0) / dur, 1); +  const DAMP   0.72 // damping to prevent overshoot 
-    const ease < 0.5 ? 2*p*p : -1+(4-2*p)*p+  let springRunning = true; 
-    scrollY start dist * ease;+  let sv = 0;           // spring velocity separate from main velocity 
 +  function step() { 
 +    if (!springRunningreturn
 +    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();     render();
-    if (p < 1) requestAnimationFrame(step);+    requestAnimationFrame(step);
   }   }
   requestAnimationFrame(step);   requestAnimationFrame(step);
Line 650: Line 673:
   content.classList.add('hidden');   content.classList.add('hidden');
 } }
- 
 function showCat(cat) { function showCat(cat) {
   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; 
-  const grid = document.getElementById('ut-card-grid')+  document.getElementById('ut-card-grid').innerHTML = ''; 
-  grid.innerHTML = ''; +  document.getElementById('ut-open-btn').href = '/wiki/doku.php?id=' + cat.wikiId;
-  cat.subs.forEach(sub => { +
-    const a = document.createElement('a'); +
-    a.className = 'ut-guide-card'; +
-    a.href = '/wiki/doku.php?id=' + sub.wikiId; +
-    a.innerHTML = `<div class="ut-guide-card-name">${sub.label}</div><div class="ut-guide-card-meta">${sub.desc}</div>`; +
-    a.addEventListener('click', e => { e.preventDefault(); onItemClick({type:'sub', cat, sub}); }); +
-    grid.appendChild(a); +
-  }); +
-  const btn = document.getElementById('ut-open-btn')+
-  btn.href = '/wiki/doku.php?id=' + cat.wikiId;+
   document.getElementById('ut-btn-label').textContent = 'Open ' + cat.label + ' overview';   document.getElementById('ut-btn-label').textContent = 'Open ' + cat.label + ' overview';
 } }
- 
 function showSub(cat, sub) { function showSub(cat, sub) {
   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; 
-  const grid = document.getElementById('ut-card-grid')+  document.getElementById('ut-card-grid').innerHTML = ''; 
-  grid.innerHTML = ''; +  document.getElementById('ut-open-btn').href = '/wiki/doku.php?id=' + sub.wikiId;
-  ['Overview','Basic Setup','Intermediate','Advanced','Video Examples','Common Mistakes'].forEach(s => { +
-    const a = document.createElement('a'); +
-    a.className = 'ut-guide-card'; +
-    a.href = '/wiki/doku.php?id=' + sub.wikiId; +
-    a.innerHTML = `<div class="ut-guide-card-name">${s}</div><div class="ut-guide-card-meta">Guide section</div>`; +
-    grid.appendChild(a); +
-  }); +
-  const btn = document.getElementById('ut-open-btn')+
-  btn.href = '/wiki/doku.php?id=' + sub.wikiId;+
   document.getElementById('ut-btn-label').textContent = 'Open ' + sub.label + ' page';   document.getElementById('ut-btn-label').textContent = 'Open ' + sub.label + ' page';
 } }
  
-// ── SPLASH PILLS ──────────────────────────────────────── +// ── SCROLL ─────────────────────────────────────────────
-const pillsEl = document.getElementById('ut-splash-pills'); +
-CATS.forEach(cat => { +
-  const p = document.createElement('button'); +
-  p.className = 'ut-pill'; +
-  p.textContent = cat.label; +
-  p.addEventListener('click', () => onItemClick({type:'cat', cat, sub:null})); +
-  pillsEl.appendChild(p); +
-}); +
- +
-// ── MOMENTUM SCROLL ────────────────────────────────────+
 const wrap = document.getElementById('ut-carousel-wrap'); const wrap = document.getElementById('ut-carousel-wrap');
-let isDragging = false+let momentumRunning = false;
-let lastY = 0; +
-let lastTime = 0;+
  
 +// Wheel sensitivity: low so small scrolls move little, fast scrolls build up
 +const WHEEL_ACCEL   = 0.18;   // fraction of deltaY added to velocity each event
 +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 startMomentum() {
 +  if (momentumRunning) return;
 +  momentumRunning = true;
 +  function step() {
 +    velocity *= FRICTION;
 +    if (Math.abs(velocity) < VEL_STOP) {
 +      velocity = 0;
 +      momentumRunning = false;
 +      return;
 +    }
 +    scrollY += velocity;
 +    render();
 +    requestAnimationFrame(step);
 +  }
 +  requestAnimationFrame(step);
 +}
 +
 +// WHEEL — accumulate velocity proportional to scroll speed
 +wrap.addEventListener('wheel', e => {
 +  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, velocity));
 +  startMomentum();
 +}, { passive: false });
 +
 +// DRAG
 wrap.addEventListener('mousedown', e => { wrap.addEventListener('mousedown', e => {
-  isDragging true; +  momentumRunning false;
-  lastY = e.clientY; +
-  lastTime = performance.now();+
   velocity = 0;   velocity = 0;
-  cancelAnimationFrame(rafId);+  isDragging = true; lastDragY = e.clientY; lastDragT = performance.now()
 +  wrap.style.cursor = 'grabbing';
   e.preventDefault();   e.preventDefault();
 }); });
- 
 window.addEventListener('mousemove', e => { window.addEventListener('mousemove', e => {
   if (!isDragging) return;   if (!isDragging) return;
   const now = performance.now();   const now = performance.now();
-  const dy = e.clientY - lastY+  const dy  = e.clientY - lastDragY
-  velocity dy / (now - lastTime + 1) * 16+  const dt  Math.max(now - lastDragT, 1); 
-  scrollY -= dy; +  velocity  = (-dy / dt) * 8
-  lastY = e.clientY; +  scrollY  -= dy; 
-  lastTime = now;+  lastDragY = e.clientY; lastDragT = now;
   render();   render();
 }); });
- 
 window.addEventListener('mouseup', () => { window.addEventListener('mouseup', () => {
   if (!isDragging) return;   if (!isDragging) return;
   isDragging = false;   isDragging = false;
 +  wrap.style.cursor = 'grab';
   startMomentum();   startMomentum();
 }); });
  
-wrap.addEventListener('wheel', e => { +// TOUCH
-  e.preventDefault(); +
-  scrollY += e.deltaY * 0.8; +
-  velocity = 0; +
-  render(); +
-}, { passive: false }); +
- +
-// touch+
 wrap.addEventListener('touchstart', e => { wrap.addEventListener('touchstart', e => {
-  isDragging true+  momentumRunning false; velocity = 0
-  lastY = e.touches[0].clientY; +  isDragging = true; lastDragY = e.touches[0].clientY; lastDragT = performance.now();
-  lastTime = performance.now(); +
-  velocity = 0; +
-  cancelAnimationFrame(rafId);+
 }, { passive: true }); }, { passive: true });
- 
 wrap.addEventListener('touchmove', e => { wrap.addEventListener('touchmove', e => {
   if (!isDragging) return;   if (!isDragging) return;
   const now = performance.now();   const now = performance.now();
-  const dy = e.touches[0].clientY - lastY+  const dy  = e.touches[0].clientY - lastDragY
-  velocity = dy / (now - lastTime + 1) * 16+  velocity  (-dy / Math.max(now - lastDragT, 1)) * 8
-  scrollY -= dy; +  scrollY  -= dy; 
-  lastY = e.touches[0].clientY; +  lastDragY = e.touches[0].clientY; lastDragT = now;
-  lastTime = now;+
   render();   render();
 }, { passive: true }); }, { passive: true });
- 
 wrap.addEventListener('touchend', () => { isDragging = false; startMomentum(); }); wrap.addEventListener('touchend', () => { isDragging = false; startMomentum(); });
- 
-function startMomentum() { 
-  cancelAnimationFrame(rafId); 
-  function step() { 
-    if (Math.abs(velocity) < 0.3) return; 
-    scrollY -= velocity; 
-    velocity *= 0.92; 
-    render(); 
-    rafId = requestAnimationFrame(step); 
-  } 
-  rafId = requestAnimationFrame(step); 
-} 
  
 // ── SEARCH ───────────────────────────────────────────── // ── SEARCH ─────────────────────────────────────────────
Line 780: Line 778:
   render();   render();
 }); });
- 
-// ── VIDEO CYCLE ───────────────────────────────────────── 
-const video = document.getElementById('ut-video'); 
-const vids = [ 
-  '/wiki/lib/tpl/bootstrap3/videos/gameplay1.mp4', 
-  '/wiki/lib/tpl/bootstrap3/videos/gameplay2.mp4', 
-  '/wiki/lib/tpl/bootstrap3/videos/gameplay3.mp4', 
-  '/wiki/lib/tpl/bootstrap3/videos/gameplay4.mp4', 
-]; 
-let vi = 0; 
-video.addEventListener('ended', () => { vi = (vi+1)%vids.length; video.src = vids[vi]; video.play(); }); 
  
 // ── INIT ─────────────────────────────────────────────── // ── INIT ───────────────────────────────────────────────
 window.addEventListener('resize', render); window.addEventListener('resize', render);
 +// start scroll so first item is near center
 +scrollY = -(document.getElementById('ut-carousel-wrap').offsetHeight / 2 - CAT_H / 2);
 render(); render();
  
  • universaltech/start.1782109143.txt.gz
  • Last modified: 2026/06/22 06:19
  • by jumpstartadmin