word-cascade.js 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. (function(){
  2. window.HPX = window.HPX || {};
  3. window.HPX['word-cascade'] = function(el){
  4. const U = window.HPX._u;
  5. const k = U.canvas(el), ctx = k.ctx;
  6. const pal = U.palette(el);
  7. const WORDS = ['AI','知识','Graph','Claude','LLM','Agent','Vector','RAG','Token','神经',
  8. 'Prompt','Chain','Skill','Code','Cloud','GPU','Flow','推理','Data','Model'];
  9. let items = [];
  10. let last = -1;
  11. let piles = {}; // column -> stack height
  12. const stop = U.loop((t) => {
  13. ctx.clearRect(0,0,k.w,k.h);
  14. if (t - last > 0.18){
  15. last = t;
  16. const w = WORDS[(Math.random()*WORDS.length)|0];
  17. items.push({
  18. text: w, x: U.rand(40, k.w-40), y: -20,
  19. vy: 0, c: pal[(Math.random()*pal.length)|0],
  20. size: U.rand(16,26), landed: false
  21. });
  22. }
  23. ctx.textAlign='center'; ctx.textBaseline='middle';
  24. for (const it of items){
  25. if (!it.landed){
  26. it.vy += 0.4;
  27. it.y += it.vy;
  28. const col = Math.round(it.x/60);
  29. const floor = k.h - (piles[col]||0) - it.size*0.6;
  30. if (it.y >= floor){
  31. it.y = floor; it.landed = true;
  32. piles[col] = (piles[col]||0) + it.size*1.1;
  33. if ((piles[col]||0) > k.h*0.8) piles[col] = 0; // reset if too high
  34. }
  35. }
  36. ctx.fillStyle = it.c;
  37. ctx.font = `700 ${it.size}px system-ui,sans-serif`;
  38. ctx.fillText(it.text, it.x, it.y);
  39. }
  40. // prune old landed
  41. if (items.length > 120){
  42. items = items.filter(i => !i.landed).concat(items.filter(i=>i.landed).slice(-60));
  43. }
  44. });
  45. return { stop(){ stop(); k.destroy(); } };
  46. };
  47. })();