Instructions to use morikomorizz/GLM-Collection with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use morikomorizz/GLM-Collection with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf morikomorizz/GLM-Collection:IQ2_M # Run inference directly in the terminal: llama cli -hf morikomorizz/GLM-Collection:IQ2_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf morikomorizz/GLM-Collection:IQ2_M # Run inference directly in the terminal: llama cli -hf morikomorizz/GLM-Collection:IQ2_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf morikomorizz/GLM-Collection:IQ2_M # Run inference directly in the terminal: ./llama-cli -hf morikomorizz/GLM-Collection:IQ2_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf morikomorizz/GLM-Collection:IQ2_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf morikomorizz/GLM-Collection:IQ2_M
Use Docker
docker model run hf.co/morikomorizz/GLM-Collection:IQ2_M
- LM Studio
- Jan
- vLLM
How to use morikomorizz/GLM-Collection with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "morikomorizz/GLM-Collection" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "morikomorizz/GLM-Collection", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/morikomorizz/GLM-Collection:IQ2_M
- Ollama
How to use morikomorizz/GLM-Collection with Ollama:
ollama run hf.co/morikomorizz/GLM-Collection:IQ2_M
- Unsloth Studio
How to use morikomorizz/GLM-Collection with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for morikomorizz/GLM-Collection to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for morikomorizz/GLM-Collection to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for morikomorizz/GLM-Collection to start chatting
- Pi
How to use morikomorizz/GLM-Collection with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf morikomorizz/GLM-Collection:IQ2_M
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "morikomorizz/GLM-Collection:IQ2_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- OpenClaw new
How to use morikomorizz/GLM-Collection with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf morikomorizz/GLM-Collection:IQ2_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "morikomorizz/GLM-Collection:IQ2_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
- Docker Model Runner
How to use morikomorizz/GLM-Collection with Docker Model Runner:
docker model run hf.co/morikomorizz/GLM-Collection:IQ2_M
- Lemonade
How to use morikomorizz/GLM-Collection with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull morikomorizz/GLM-Collection:IQ2_M
Run and chat with the model
lemonade run user.GLM-Collection-IQ2_M
List all available models
lemonade list
- Hermes Agent
How to use morikomorizz/GLM-Collection with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf morikomorizz/GLM-Collection:IQ2_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default morikomorizz/GLM-Collection:IQ2_M
Run Hermes
hermes
- Atomic Chat
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Sunset Drive</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| html, body { width: 100%; height: 100%; overflow: hidden; background: #000; } | |
| canvas { display: block; } | |
| .label { | |
| position: fixed; | |
| top: 28px; | |
| left: 32px; | |
| color: rgba(255, 200, 160, 0.55); | |
| font-family: 'Georgia', serif; | |
| font-size: 13px; | |
| letter-spacing: 4px; | |
| text-transform: uppercase; | |
| pointer-events: none; | |
| z-index: 10; | |
| text-shadow: 0 1px 2px rgba(0,0,0,0.5); | |
| } | |
| .label::before { | |
| content: ''; | |
| display: block; | |
| width: 32px; | |
| height: 1px; | |
| background: rgba(255, 200, 160, 0.55); | |
| margin-bottom: 10px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="c"></canvas> | |
| <div class="label">Sunset Drive</div> | |
| <script> | |
| // === Canvas Setup === | |
| const canvas = document.getElementById('c'); | |
| const ctx = canvas.getContext('2d'); | |
| let W = 0, H = 0, DPR = 1; | |
| let worldOffset = 0; | |
| let time = 0; | |
| let lastTime = 0; | |
| const SPEED = 220; | |
| let dustParticles = []; | |
| let carScale = 1; | |
| function resize() { | |
| DPR = Math.min(window.devicePixelRatio || 1, 2); | |
| W = window.innerWidth; | |
| H = window.innerHeight; | |
| canvas.width = W * DPR; | |
| canvas.height = H * DPR; | |
| canvas.style.width = W + 'px'; | |
| canvas.style.height = H + 'px'; | |
| ctx.setTransform(DPR, 0, 0, DPR, 0, 0); | |
| carScale = Math.max(0.45, Math.min(W / 800, H / 600, 1.3)); | |
| } | |
| resize(); | |
| window.addEventListener('resize', resize); | |
| // === Helpers === | |
| function hash(n) { | |
| const s = Math.sin(n * 12.9898 + 78.233) * 43758.5453; | |
| return s - Math.floor(s); | |
| } | |
| function layout() { | |
| return { | |
| horizonY: H * 0.62, | |
| roadTop: H * 0.72, | |
| wheelY: H * 0.82, | |
| carX: W * 0.42, | |
| sunX: W * 0.72, | |
| sunY: H * 0.62 - 25, | |
| }; | |
| } | |
| // === Sky === | |
| function drawSky() { | |
| const L = layout(); | |
| const horizonY = L.horizonY; | |
| // Sky gradient (sunset) | |
| const g = ctx.createLinearGradient(0, 0, 0, horizonY); | |
| g.addColorStop(0, '#0a0820'); | |
| g.addColorStop(0.15, '#1c1230'); | |
| g.addColorStop(0.4, '#3e1f4a'); | |
| g.addColorStop(0.65, '#7a2845'); | |
| g.addColorStop(0.85, '#c64a36'); | |
| g.addColorStop(1, '#e87838'); | |
| ctx.fillStyle = g; | |
| ctx.fillRect(0, 0, W, horizonY); | |
| // Sun | |
| const sunX = L.sunX; | |
| const sunY = L.sunY; | |
| const sunR = Math.min(W, H) * 0.06; | |
| // Sun halo | |
| const halo = ctx.createRadialGradient(sunX, sunY, 0, sunX, sunY, sunR * 12); | |
| halo.addColorStop(0, 'rgba(255, 200, 130, 0.4)'); | |
| halo.addColorStop(0.1, 'rgba(255, 170, 110, 0.22)'); | |
| halo.addColorStop(0.3, 'rgba(255, 130, 90, 0.08)'); | |
| halo.addColorStop(1, 'rgba(255, 130, 90, 0)'); | |
| ctx.fillStyle = halo; | |
| ctx.fillRect(0, 0, W, horizonY + 30); | |
| // Sun disc | |
| const sunGrad = ctx.createRadialGradient(sunX, sunY, 0, sunX, sunY, sunR); | |
| sunGrad.addColorStop(0, 'rgba(255, 248, 220, 1)'); | |
| sunGrad.addColorStop(0.5, 'rgba(255, 220, 150, 1)'); | |
| sunGrad.addColorStop(0.85, 'rgba(255, 170, 100, 0.9)'); | |
| sunGrad.addColorStop(1, 'rgba(255, 150, 90, 0)'); | |
| ctx.fillStyle = sunGrad; | |
| ctx.beginPath(); | |
| ctx.arc(sunX, sunY, sunR, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Stars | |
| for (let i = 0; i < 80; i++) { | |
| const sx = hash(i * 23.3) * W; | |
| const sy = hash(i * 7.7) * horizonY * 0.45; | |
| const sr = hash(i * 11.1) * 1.2 + 0.3; | |
| const tw = (Math.sin(time * 1.5 + i * 2.1) * 0.5 + 0.5); | |
| const fade = 1 - sy / (horizonY * 0.45); | |
| const alpha = tw * 0.6 * fade; | |
| ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; | |
| ctx.beginPath(); | |
| ctx.arc(sx, sy, sr, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| // Clouds | |
| drawClouds(0.015, horizonY); | |
| // Horizon glow | |
| const refGrad = ctx.createLinearGradient(0, horizonY - 8, 0, horizonY + 25); | |
| refGrad.addColorStop(0, 'rgba(255, 180, 100, 0)'); | |
| refGrad.addColorStop(0.4, 'rgba(255, 150, 80, 0.5)'); | |
| refGrad.addColorStop(1, 'rgba(120, 60, 50, 0)'); | |
| ctx.fillStyle = refGrad; | |
| ctx.fillRect(0, horizonY - 8, W, 35); | |
| } | |
| function drawClouds(speed, horizonY) { | |
| const interval = 320; | |
| const offset = worldOffset * speed; | |
| const firstWorldX = Math.floor(offset / interval) * interval; | |
| const lastWorldX = Math.ceil((offset + W + interval) / interval) * interval; | |
| for (let worldX = firstWorldX; worldX <= lastWorldX; worldX += interval) { | |
| const screenX = worldX - offset; | |
| const idx = Math.round(worldX / interval); | |
| const cx = screenX + hash(idx * 5.3) * 200 - 100; | |
| const cy = hash(idx * 7.7) * horizonY * 0.5 + 20; | |
| const cw = 180 + hash(idx * 11.1) * 220; | |
| const ch = 18 + hash(idx * 13.3) * 22; | |
| const opacity = 0.25 + hash(idx * 17.7) * 0.3; | |
| drawSoftCloud(cx, cy, cw, ch, opacity); | |
| } | |
| } | |
| function drawSoftCloud(x, y, w, h, opacity) { | |
| ctx.fillStyle = `rgba(70, 35, 55, ${opacity})`; | |
| ctx.beginPath(); | |
| ctx.ellipse(x, y, w * 0.5, h, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.ellipse(x + w * 0.2, y - h * 0.3, w * 0.35, h * 0.8, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.ellipse(x - w * 0.25, y + h * 0.1, w * 0.3, h * 0.7, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.ellipse(x + w * 0.4, y + h * 0.2, w * 0.25, h * 0.5, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| // === Birds === | |
| function drawBirds() { | |
| const speed = 0.03; | |
| const offset = worldOffset * speed; | |
| for (let i = 0; i < 5; i++) { | |
| const baseX = i * 380 + hash(i * 13.7) * 200; | |
| const totalRange = W + 400; | |
| const screenX = ((baseX - offset) % totalRange + totalRange) % totalRange - 200; | |
| const birdY = hash(i * 7.3) * H * 0.22 + 40; | |
| const flap = Math.sin(time * 4 + i * 1.3) * 4; | |
| const size = 4 + hash(i * 31.7) * 3; | |
| if (screenX > -30 && screenX < W + 30) { | |
| ctx.strokeStyle = 'rgba(30, 20, 35, 0.7)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.moveTo(screenX - size, birdY + flap * 0.5); | |
| ctx.lineTo(screenX, birdY - flap * 0.5); | |
| ctx.lineTo(screenX + size, birdY + flap * 0.5); | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| // === Mountains === | |
| function drawMountainLayer(speed, color, baseY, params) { | |
| const offset = worldOffset * speed; | |
| ctx.fillStyle = color; | |
| ctx.beginPath(); | |
| ctx.moveTo(-10, H + 50); | |
| for (let x = -10; x <= W + 10; x += 4) { | |
| const wx = x + offset; | |
| let h = 0; | |
| for (const p of params) { | |
| h += Math.sin(wx * p.freq + p.phase) * p.amp; | |
| } | |
| const height = Math.max(15, h + 80); | |
| ctx.lineTo(x, baseY - height); | |
| } | |
| ctx.lineTo(W + 10, H + 50); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| function drawMountains() { | |
| const L = layout(); | |
| const horizonY = L.horizonY; | |
| drawMountainLayer(0.04, 'rgba(60, 30, 60, 0.55)', horizonY + 5, [ | |
| { freq: 0.003, amp: 35, phase: 0 }, | |
| { freq: 0.007, amp: 22, phase: 1.5 }, | |
| { freq: 0.013, amp: 10, phase: 2.8 }, | |
| ]); | |
| drawMountainLayer(0.08, 'rgba(45, 22, 48, 0.75)', horizonY + 12, [ | |
| { freq: 0.004, amp: 45, phase: 0.7 }, | |
| { freq: 0.009, amp: 22, phase: 2.1 }, | |
| { freq: 0.018, amp: 10, phase: 4.2 }, | |
| ]); | |
| drawMountainLayer(0.15, 'rgba(50, 25, 35, 0.85)', horizonY + 22, [ | |
| { freq: 0.006, amp: 30, phase: 1.2 }, | |
| { freq: 0.014, amp: 15, phase: 3.5 }, | |
| { freq: 0.025, amp: 7, phase: 5.7 }, | |
| ]); | |
| } | |
| // === Far treeline === | |
| function drawFarTreeline() { | |
| const L = layout(); | |
| const baseY = L.horizonY + 22; | |
| const offset = worldOffset * 0.22; | |
| ctx.fillStyle = 'rgba(15, 10, 20, 0.85)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(-10, H + 50); | |
| ctx.lineTo(-10, baseY); | |
| for (let x = -10; x <= W + 10; x += 3) { | |
| const wx = x + offset; | |
| const t1 = Math.sin(wx * 0.05) * 4; | |
| const t2 = Math.sin(wx * 0.13) * 3; | |
| const t3 = Math.sin(wx * 0.27) * 2; | |
| const t4 = Math.max(0, Math.sin(wx * 0.02) - 0.3) * 15; | |
| const h = 6 + Math.abs(t1 + t2 + t3) + t4; | |
| ctx.lineTo(x, baseY - h); | |
| } | |
| ctx.lineTo(W + 10, H + 50); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| // === Mid trees === | |
| function drawMidTrees() { | |
| const L = layout(); | |
| const baseY = L.horizonY + 35; | |
| const interval = 200; | |
| const speed = 0.42; | |
| const offset = worldOffset * speed; | |
| const firstWorldX = Math.floor(offset / interval) * interval; | |
| const lastWorldX = Math.ceil((offset + W + interval) / interval) * interval; | |
| for (let worldX = firstWorldX; worldX <= lastWorldX; worldX += interval) { | |
| const screenX = worldX - offset; | |
| const idx = Math.round(worldX / interval); | |
| if (hash(idx * 3.7) < 0.25) continue; | |
| const treeType = hash(idx * 11.7); | |
| const treeX = screenX + hash(idx * 5.3) * 100 - 50; | |
| const treeHeight = 35 + hash(idx * 7.7) * 30; | |
| const treeY = baseY + hash(idx * 13.3) * 8; | |
| if (treeType < 0.5) { | |
| drawPineTree(treeX, treeY, treeHeight); | |
| } else { | |
| drawRoundTree(treeX, treeY, treeHeight); | |
| } | |
| } | |
| } | |
| function drawPineTree(x, y, h) { | |
| ctx.fillStyle = '#1a0e15'; | |
| ctx.fillRect(x - 2, y - h * 0.25, 4, h * 0.25); | |
| ctx.fillStyle = '#1a2018'; | |
| const layers = 4; | |
| for (let i = 0; i < layers; i++) { | |
| const ly = y - h * 0.25 - i * h * 0.2; | |
| const lw = (layers - i) * 7 + 4; | |
| ctx.beginPath(); | |
| ctx.moveTo(x - lw, ly); | |
| ctx.lineTo(x + lw, ly); | |
| ctx.lineTo(x, ly - h * 0.25); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| } | |
| function drawRoundTree(x, y, h) { | |
| ctx.fillStyle = '#1a0e15'; | |
| ctx.fillRect(x - 2, y - h * 0.4, 4, h * 0.4); | |
| ctx.fillStyle = '#1a2018'; | |
| ctx.beginPath(); | |
| ctx.arc(x, y - h * 0.5, h * 0.32, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(x - h * 0.18, y - h * 0.4, h * 0.22, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(x + h * 0.18, y - h * 0.4, h * 0.22, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(x, y - h * 0.7, h * 0.22, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| // === Poles === | |
| function drawPoles() { | |
| const L = layout(); | |
| const baseY = L.horizonY + 32; | |
| const interval = 280; | |
| const speed = 0.65; | |
| const offset = worldOffset * speed; | |
| const firstIdx = Math.floor(offset / interval); | |
| const lastIdx = Math.ceil((offset + W) / interval) + 1; | |
| // Wires | |
| ctx.strokeStyle = 'rgba(15, 8, 12, 0.7)'; | |
| ctx.lineWidth = 1; | |
| for (let i = firstIdx; i <= lastIdx; i++) { | |
| const x1 = i * interval - offset + hash(i * 5.3) * 50 - 25; | |
| const x2 = (i + 1) * interval - offset + hash((i + 1) * 5.3) * 50 - 25; | |
| const h1 = 90 + hash(i * 7.7) * 20; | |
| const h2 = 90 + hash((i + 1) * 7.7) * 20; | |
| if (x2 < -50 || x1 > W + 50) continue; | |
| for (let w = 0; w < 3; w++) { | |
| const y1 = baseY - h1 + 10 + w * 12; | |
| const y2 = baseY - h2 + 10 + w * 12; | |
| const sag = 6 + w * 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(x1, y1); | |
| ctx.quadraticCurveTo((x1 + x2) / 2, (y1 + y2) / 2 + sag, x2, y2); | |
| ctx.stroke(); | |
| } | |
| } | |
| // Poles | |
| for (let i = firstIdx; i <= lastIdx; i++) { | |
| const screenX = i * interval - offset; | |
| const x = screenX + hash(i * 5.3) * 50 - 25; | |
| const h = 90 + hash(i * 7.7) * 20; | |
| if (x < -30 || x > W + 30) continue; | |
| drawPole(x, baseY, h); | |
| } | |
| } | |
| function drawPole(x, baseY, h) { | |
| ctx.fillStyle = '#1f1015'; | |
| ctx.fillRect(x - 2, baseY - h, 4, h); | |
| ctx.fillRect(x - 14, baseY - h + 8, 28, 3); | |
| ctx.fillRect(x - 10, baseY - h + 18, 20, 3); | |
| ctx.fillStyle = '#3a2a28'; | |
| ctx.beginPath(); | |
| ctx.arc(x - 12, baseY - h + 8, 2.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(x + 12, baseY - h + 8, 2.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(x - 8, baseY - h + 18, 2.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(x + 8, baseY - h + 18, 2.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| // === Road === | |
| function drawRoad() { | |
| const L = layout(); | |
| const roadTop = L.roadTop; | |
| const wheelY = L.wheelY; | |
| const sunX = L.sunX; | |
| // Road surface | |
| const roadGrad = ctx.createLinearGradient(0, roadTop, 0, H); | |
| roadGrad.addColorStop(0, '#1a1520'); | |
| roadGrad.addColorStop(0.3, '#252028'); | |
| roadGrad.addColorStop(1, '#0e0a14'); | |
| ctx.fillStyle = roadGrad; | |
| ctx.fillRect(0, roadTop, W, H - roadTop); | |
| // Warm sky light on road | |
| const skyLight = ctx.createLinearGradient(0, roadTop, 0, roadTop + 30); | |
| skyLight.addColorStop(0, 'rgba(180, 100, 80, 0.35)'); | |
| skyLight.addColorStop(1, 'rgba(120, 60, 50, 0)'); | |
| ctx.fillStyle = skyLight; | |
| ctx.fillRect(0, roadTop, W, 30); | |
| // Sun reflection on road | |
| const sunRefGrad = ctx.createRadialGradient(sunX, wheelY, 0, sunX, wheelY, 180); | |
| sunRefGrad.addColorStop(0, 'rgba(255, 180, 100, 0.22)'); | |
| sunRefGrad.addColorStop(0.5, 'rgba(255, 150, 80, 0.08)'); | |
| sunRefGrad.addColorStop(1, 'rgba(255, 150, 80, 0)'); | |
| ctx.fillStyle = sunRefGrad; | |
| ctx.fillRect(sunX - 180, roadTop, 360, H - roadTop); | |
| // Dashed center line | |
| const dashLen = 50; | |
| const dashGap = 50; | |
| const dashInterval = dashLen + dashGap; | |
| const dashOffset = worldOffset % dashInterval; | |
| ctx.fillStyle = 'rgba(220, 170, 90, 0.4)'; | |
| for (let x = -dashOffset; x < W; x += dashInterval) { | |
| ctx.fillRect(x, wheelY + 8, dashLen, 3); | |
| } | |
| // Road texture (small dots) | |
| const texInterval = 40; | |
| const texOffset = worldOffset % texInterval; | |
| ctx.fillStyle = 'rgba(60, 50, 60, 0.4)'; | |
| for (let x = -texOffset - texInterval; x < W + texInterval; x += texInterval) { | |
| const idx = Math.floor((x + worldOffset) / texInterval); | |
| const px = x + hash(idx * 31.7) * 30 - 15; | |
| const py = roadTop + 35 + hash(idx * 17.3) * (H - roadTop - 50); | |
| ctx.fillRect(px, py, 1, 1); | |
| } | |
| // Road edge line (warm) | |
| ctx.fillStyle = 'rgba(180, 110, 70, 0.4)'; | |
| ctx.fillRect(0, roadTop - 1, W, 2); | |
| } | |
| // === Car === | |
| function drawCar() { | |
| const L = layout(); | |
| const carX = L.carX; | |
| const groundY = L.wheelY; | |
| // Body bob (suspension) | |
| const bobY = Math.sin(time * 7) * 1.2 + Math.sin(time * 11) * 0.4; | |
| const tiltAngle = Math.sin(time * 3) * 0.006; | |
| ctx.save(); | |
| ctx.translate(carX, groundY + bobY); | |
| ctx.scale(carScale, carScale); | |
| ctx.rotate(tiltAngle); | |
| const wheelR = 28; | |
| const wheel1X = -85; | |
| const wheel2X = 85; | |
| // Shadow under car | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; | |
| ctx.beginPath(); | |
| ctx.ellipse(0, 2, 145, 7, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.25)'; | |
| ctx.beginPath(); | |
| ctx.ellipse(0, 5, 165, 12, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // === Car body === | |
| ctx.fillStyle = '#1e1428'; | |
| ctx.beginPath(); | |
| ctx.moveTo(115, -10); | |
| ctx.quadraticCurveTo(125, -8, 128, -25); | |
| ctx.quadraticCurveTo(128, -40, 115, -48); | |
| ctx.lineTo(50, -55); | |
| ctx.quadraticCurveTo(30, -62, 18, -88); | |
| ctx.lineTo(-25, -92); | |
| ctx.quadraticCurveTo(-40, -88, -52, -60); | |
| ctx.lineTo(-100, -55); | |
| ctx.quadraticCurveTo(-118, -50, -120, -28); | |
| ctx.quadraticCurveTo(-125, -10, -115, -10); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // === Warm rim light along the top === | |
| ctx.fillStyle = 'rgba(220, 130, 80, 0.4)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(115, -45); | |
| ctx.lineTo(50, -52); | |
| ctx.quadraticCurveTo(30, -59, 18, -85); | |
| ctx.lineTo(-25, -89); | |
| ctx.quadraticCurveTo(-40, -85, -52, -57); | |
| ctx.lineTo(-100, -52); | |
| ctx.lineTo(-100, -49); | |
| ctx.lineTo(-52, -54); | |
| ctx.quadraticCurveTo(-40, -82, -25, -86); | |
| ctx.lineTo(18, -82); | |
| ctx.quadraticCurveTo(30, -56, 50, -49); | |
| ctx.lineTo(115, -42); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // === Side body line === | |
| ctx.strokeStyle = 'rgba(180, 100, 60, 0.3)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.moveTo(118, -22); | |
| ctx.lineTo(-118, -22); | |
| ctx.stroke(); | |
| // Lower body shadow | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(115, -10); | |
| ctx.lineTo(-115, -10); | |
| ctx.lineTo(-115, -16); | |
| ctx.lineTo(115, -16); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // === Windows === | |
| ctx.fillStyle = '#0a0815'; | |
| ctx.beginPath(); | |
| ctx.moveTo(48, -55); | |
| ctx.quadraticCurveTo(30, -62, 20, -85); | |
| ctx.lineTo(-22, -88); | |
| ctx.lineTo(-22, -58); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Window glass | |
| const winGrad = ctx.createLinearGradient(0, -85, 0, -55); | |
| winGrad.addColorStop(0, 'rgba(180, 110, 90, 0.75)'); | |
| winGrad.addColorStop(0.5, 'rgba(120, 70, 80, 0.65)'); | |
| winGrad.addColorStop(1, 'rgba(80, 50, 70, 0.7)'); | |
| ctx.fillStyle = winGrad; | |
| ctx.beginPath(); | |
| ctx.moveTo(46, -56); | |
| ctx.quadraticCurveTo(30, -62, 22, -83); | |
| ctx.lineTo(-20, -86); | |
| ctx.lineTo(-20, -58); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Window divider (B-pillar) | |
| ctx.fillStyle = '#1a1020'; | |
| ctx.fillRect(-2, -88, 4, 30); | |
| // Door line | |
| ctx.strokeStyle = 'rgba(20, 10, 20, 0.5)'; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, -58); | |
| ctx.lineTo(0, -10); | |
| ctx.stroke(); | |
| // === Wheel wells === | |
| ctx.fillStyle = '#080510'; | |
| ctx.beginPath(); | |
| ctx.arc(wheel1X, -wheelR, wheelR + 5, Math.PI, 2 * Math.PI, false); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(wheel2X, -wheelR, wheelR + 5, Math.PI, 2 * Math.PI, false); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // === Wheels === | |
| const wheelAngle = time * 9; | |
| drawWheel(wheel1X, -wheelR, wheelR, wheelAngle); | |
| drawWheel(wheel2X, -wheelR, wheelR, wheelAngle); | |
| // === Headlight === | |
| const hlX = 122; | |
| const hlY = -32; | |
| const hlGrad = ctx.createRadialGradient(hlX, hlY, 0, hlX, hlY, 40); | |
| hlGrad.addColorStop(0, 'rgba(255, 230, 150, 0.7)'); | |
| hlGrad.addColorStop(0.2, 'rgba(255, 200, 100, 0.3)'); | |
| hlGrad.addColorStop(1, 'rgba(255, 180, 80, 0)'); | |
| ctx.fillStyle = hlGrad; | |
| ctx.beginPath(); | |
| ctx.arc(hlX, hlY, 40, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = 'rgba(255, 240, 180, 0.95)'; | |
| ctx.beginPath(); | |
| ctx.ellipse(hlX, hlY, 5, 4, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // === Taillight === | |
| const tlX = -120; | |
| const tlY = -28; | |
| const tlGrad = ctx.createRadialGradient(tlX, tlY, 0, tlX, tlY, 22); | |
| tlGrad.addColorStop(0, 'rgba(220, 60, 60, 0.7)'); | |
| tlGrad.addColorStop(0.3, 'rgba(180, 40, 40, 0.3)'); | |
| tlGrad.addColorStop(1, 'rgba(180, 40, 40, 0)'); | |
| ctx.fillStyle = tlGrad; | |
| ctx.beginPath(); | |
| ctx.arc(tlX, tlY, 22, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = 'rgba(220, 80, 80, 0.95)'; | |
| ctx.fillRect(-126, -32, 6, 8); | |
| // === Light beam from headlight === | |
| ctx.save(); | |
| ctx.globalCompositeOperation = 'lighter'; | |
| const beamGrad = ctx.createLinearGradient(120, -10, 280, 5); | |
| beamGrad.addColorStop(0, 'rgba(255, 220, 150, 0.18)'); | |
| beamGrad.addColorStop(1, 'rgba(255, 220, 150, 0)'); | |
| ctx.fillStyle = beamGrad; | |
| ctx.beginPath(); | |
| ctx.moveTo(118, -28); | |
| ctx.lineTo(280, -15); | |
| ctx.lineTo(280, 8); | |
| ctx.lineTo(118, 2); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.restore(); | |
| ctx.restore(); | |
| } | |
| function drawWheel(wx, wy, r, angle) { | |
| ctx.save(); | |
| ctx.translate(wx, wy); | |
| // Outer tire | |
| ctx.fillStyle = '#080810'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, r, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Tire outer ring | |
| ctx.strokeStyle = '#1a1a1f'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, r, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| // Rim base | |
| ctx.fillStyle = '#2a2a35'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, r * 0.65, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Rim outer ring | |
| ctx.strokeStyle = '#4a4a55'; | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, r * 0.65, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| // Motion blur ring (subtle haze inside rim) | |
| ctx.fillStyle = 'rgba(140, 140, 155, 0.12)'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, r * 0.6, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Rotating spokes | |
| ctx.save(); | |
| ctx.rotate(angle); | |
| ctx.strokeStyle = 'rgba(180, 180, 195, 0.55)'; | |
| ctx.lineWidth = 2.5; | |
| for (let i = 0; i < 5; i++) { | |
| const a = (i / 5) * Math.PI * 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, 0); | |
| ctx.lineTo(Math.cos(a) * r * 0.55, Math.sin(a) * r * 0.55); | |
| ctx.stroke(); | |
| } | |
| // Hub | |
| ctx.fillStyle = '#5a5a65'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, r * 0.15, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = '#8a8a95'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, r * 0.08, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // Warm highlight on tire (from sunset) | |
| ctx.fillStyle = 'rgba(220, 140, 80, 0.15)'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, r, -Math.PI * 0.25, Math.PI * 0.25); | |
| ctx.lineTo(0, 0); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| // === Dust particles === | |
| function updateAndDrawDust(dt) { | |
| const L = layout(); | |
| // Spawn new particles (exhaust smoke) | |
| if (dustParticles.length < 50 && Math.random() < 0.4) { | |
| dustParticles.push({ | |
| x: L.carX - 115 * carScale + (Math.random() - 0.5) * 8, | |
| y: L.wheelY - 8 * carScale + (Math.random() - 0.5) * 6, | |
| vx: -SPEED * 0.5 - Math.random() * 50, | |
| vy: -10 - Math.random() * 15, | |
| size: 3 + Math.random() * 5, | |
| life: 1.0, | |
| decay: 0.008 + Math.random() * 0.012, | |
| }); | |
| } | |
| for (let i = dustParticles.length - 1; i >= 0; i--) { | |
| const p = dustParticles[i]; | |
| p.x += p.vx * dt; | |
| p.y += p.vy * dt; | |
| p.vy += 5 * dt; | |
| p.vy *= 0.99; | |
| p.life -= p.decay; | |
| p.size += 6 * dt; | |
| if (p.life <= 0 || p.x < -50) { | |
| dustParticles.splice(i, 1); | |
| continue; | |
| } | |
| ctx.fillStyle = `rgba(140, 110, 100, ${p.life * 0.22})`; | |
| ctx.beginPath(); | |
| ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| // === Foreground === | |
| function drawForeground() { | |
| const baseY = H - 5; | |
| // Dark ground silhouette | |
| ctx.fillStyle = '#06040a'; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, H); | |
| ctx.lineTo(0, baseY - 15); | |
| const offset = worldOffset * 1.5; | |
| for (let x = 0; x <= W; x += 4) { | |
| const wx = x + offset; | |
| const h = 15 + Math.sin(wx * 0.05) * 6 + Math.sin(wx * 0.13) * 4 + Math.sin(wx * 0.3) * 2; | |
| ctx.lineTo(x, baseY - h); | |
| } | |
| ctx.lineTo(W, H); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Grass blades | |
| const bladeInterval = 15; | |
| const bladeOffset = (worldOffset * 1.5) % bladeInterval; | |
| for (let x = -bladeOffset - bladeInterval; x < W + bladeInterval; x += bladeInterval) { | |
| const idx = Math.floor((x + worldOffset * 1.5) / bladeInterval); | |
| const bx = x + hash(idx * 7.7) * 10 - 5; | |
| const bh = 10 + hash(idx * 11.3) * 18; | |
| const slant = hash(idx * 23.1) * 6 - 3; | |
| ctx.strokeStyle = `rgba(15, 10, 20, ${0.7 + hash(idx * 31.7) * 0.3})`; | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.moveTo(bx, baseY - 5); | |
| ctx.lineTo(bx + slant, baseY - 5 - bh); | |
| ctx.stroke(); | |
| } | |
| } | |
| // === Main loop === | |
| function animate(now) { | |
| if (!lastTime) lastTime = now; | |
| const dt = Math.min(0.05, (now - lastTime) / 1000); | |
| lastTime = now; | |
| time += dt; | |
| worldOffset += SPEED * dt; | |
| ctx.clearRect(0, 0, W, H); | |
| drawSky(); | |
| drawBirds(); | |
| drawMountains(); | |
| drawFarTreeline(); | |
| drawMidTrees(); | |
| drawPoles(); | |
| drawRoad(); | |
| drawCar(); | |
| updateAndDrawDust(dt); | |
| drawForeground(); | |
| requestAnimationFrame(animate); | |
| } | |
| requestAnimationFrame(animate); | |
| </script> | |
| </body> | |
| </html> |