← Semua tutor

Hasil akhir · tombol yang ikut mendekat

Tombol CTA yang ikut mendekat.

Sebelum · tombol CTA biasa

Sesudah · tombol magnet dari asset.inwebsite.online

Coba sendiri ↗

Dua web yang sama, bedanya satu snippet. Di kanan, tombol Ceritakan usahamu tertarik ke arah kursor yang mendekat, lalu kembali ke tempatnya. Di HP efek ini tidak ada, dan tombolnya tetap bisa ditekan seperti biasa.

Langkah 1 · Siapkan dulu

Yang perlu ada sebelum mulai.

  • Editor kode apa saja. VS Code, Cursor, atau yang kamu biasa pakai.
  • Node.js dan proyek React. Snippet berjalan di React dan memakai kelas Tailwind CSS.
  • AI agent (opsional, tapi paling cepat). Di panduan ini contohnya Claude Code.
  • Akun InWebAsset. Mencari dan merencanakan gratis tanpa login, tapi kode hanya bisa diambil setelah login.
Perlu loginTanpa login kamu bisa menjelajah, melihat pratinjau, dan memakai MCP untuk mencari. Tombol Copy kode dan API key MCP baru bisa dipakai setelah Sign in.
Batas harian: 5 salinanAkun gratis boleh menyalin kode snippet 5 kali sehari. Setiap salinan dihitung, termasuk snippet yang pernah kamu salin, dan kuotanya dipakai bersama antara situs dan MCP. Akun Pro tanpa batas.

Mencari, melihat pratinjau, dan perencanaan lewat agent tidak memakai kuota.

Langkah 2 · Jelajahi katalog

Pilih desain yang mau diambil.

Buka asset.inwebsite.online. Katalognya berisi 172 snippet. Persempit lewat kategori, tempat pemakaian, atau kata kunci.

  • Pilih kategori Micro Interactions.
  • Di Where it goes, pilih Call to action.
  • Ketik magnetic di kolom cari untuk mempersempit.
  • Arahkan kursor ke kartu untuk melihat pratinjau bergerak, lalu klik Magnetic Button.
REKAMAN · KATALOG
REKAMAN · HALAMAN SNIPPET

Langkah 3 · Buka halaman snippet

Lihat dulu, baru ambil.

  • Tab Preview: dekatkan kursor ke salah satu tombol. Tombolnya tertarik ke kursor, labelnya bergeser sedikit lebih jauh, dan kursornya berganti gambar.
  • Tab Code: sebelum login, kodenya terkunci. Muncul tombol Sign in to copy.
  • Assets in this snippet: tiga gambar kursor (tangan, klik, panah merah muda) yang bisa ditukar dengan kursor lain dari keluarga yang sama.
  • Install: npm i gsap @gsap/react. Syaratnya React + Tailwind CSS.
  • For agents: ada tombol Copy prompt dan tautan registry untuk AI agent.
IngatUntuk menyalin kode kamu harus login, dan akun gratis dibatasi 5 salinan per hari.

Langkah 4 · Ambil kodenya

Dua jalur, pilih satu.

JALUR A · LEBIH CEPATPakai MCP lewat AI agent

Agent mencari, memilih, dan memasang snippet sendiri. Buka halaman For agents, login, buat API key, lalu salin perintahnya.

$claude mcp add --transport http InWebAsset https://asset.inwebsite.online/mcp --header "Authorization: Bearer <API key-mu>"

Perintah di atas untuk Claude Code. Halaman itu juga punya versi Claude Desktop, Cursor, dan VS Code. Jangan pernah membagikan API key-mu.

Mencari dan perencanaan tanpa kunci. Kodenya (get_snippet) baru keluar dengan API key, dan memakai jatah 5 salinan akun gratis.

JALUR B · MANUALSalin source code

Login, buka halaman snippet, tekan Copy di tab Code. Simpan sebagai komponen, lalu pasang sendiri atau kirim ke agent.

$ npm i gsap @gsap/react

Simpan sebagai components/MagneticButton.jsx. Tiap Copy memakai 1 dari 5 jatah harian akun gratis.

REKAMAN · HALAMAN FOR AGENTS

Langkah 5 · Beri konteks ke agent

Bilang tombol mana yang diganti.

Snippet aslinya kartu demo berisi judul, tiga tombol, dan kursor kustom. Di web contoh yang diganti cuma tombol CTA di hero, jadi agent perlu tahu tombolnya, apa yang terjadi saat diklik, dan apa yang dibuang. Tanpa itu, kartu "Ready when you are." bisa ikut muncul di hero.

Bagian web tombol CTA di heroFile Hero.jsxTombol Ceritakan usahamu →Klik turun ke #kontakWarna #e8461eLayar sentuh tanpa efek
Kalau pakai Jalur B: tempel kodenya setelah prompt ini
Ini kode komponen dari asset.inwebsite.online. Pakai untuk tombol CTA di hero (src/sections/Hero.jsx, di dalam .hero__actions), menggantikan tautan "Ceritakan usahamu →". Teks, href="#kontak", dan onClick lama (preventDefault lalu scrollToId("kontak")) harus tetap. Buang kartu, judul, latar titik, kursor kustom, dan self-demo; yang dipakai cuma tombol dan fisika magnetnya. Bentuk dan ukuran sama dengan .btn .btn--accent (pil, padding 14px 22px, 16px), warna aksen #e8461e, saat disorot jadi #141311. Efek magnet hanya untuk mouse/trackpad: cek matchMedia("(hover: hover) and (pointer: fine)"), di layar sentuh tombol tetap tombol biasa. Ganti ease elastic jadi power3.out. Proyek saya React + CSS biasa tanpa Tailwind: pasang Tailwind v4 tanpa preflight supaya gaya lama tidak berubah.

<tempel kode di sini>
contoh · Claude Code (Jalur A)
# sekali saja: sambungkan MCP
$ claude mcp add --transport http InWebAsset https://asset.inwebsite.online/mcp --header "Authorization: Bearer <API key-mu>"

# masuk ke folder proyek, jalankan agent
$ claude

> Pakai InWebAsset MCP. Ambil snippet micro-motion/magnetic-button dan pakai untuk tombol CTA di hero (src/sections/Hero.jsx, .hero__actions), menggantikan tautan "Ceritakan usahamu →". Teks, href="#kontak", dan onClick lama (scrollToId("kontak")) tetap. Yang dipakai cuma tombol dan fisika magnetnya: buang kartu demo, judul, kursor kustom, dan self-demo. Efek hanya untuk mouse/trackpad (cek pointer: fine); di HP tombol tetap biasa. Proyek saya React + CSS biasa tanpa Tailwind, jadi pasang Tailwind v4 tanpa preflight. Kirim look situs (terang, aksen #e8461e, sudut bulat, gerak tenang, tempo sedang) ke get_snippet dan terapkan saran "Fit this site". Cek dulu dengan includeCode:false, lalu ambil kodenya sekali. Jangan ubah bagian lain.

Yang kamu dapat

Snippet itu titik awal.

Kode di kanan adalah komponen yang terpasang di web contoh. Ini yang diubah dari versi asli di katalog:

  • kartu demokartu krem, judul, latar titik, tiga kursor kustom, dan self-demo (autoPlay) dibuang. Kursor bergambar kartun terlalu ramai untuk situs ini.
  • variansolid/outline/pill jadi gaya situs accent dan line. Hero ini hanya punya satu CTA, jadi yang terpasang satu tombol.
  • buttonjadi <a href="#kontak"> dengan onClick lama, jadi tetap turun ke form kontak.
  • warnaungu, kuning, dan limau jadi aksen #e8461e, saat disorot #141311. Ukuran dan bentuknya sama dengan tombol lama.
  • jangkauankursor dibaca di seluruh jendela, bukan hanya di dalam kartu demo, jadi tarikan mulai 72 px sebelum tombol tersentuh.
  • layar sentuhmagnet hanya aktif bila (hover: hover) and (pointer: fine) cocok. Di HP tidak ada listener dan transform, jadi tap langsung menjalankan onClick. Efeknya bonus untuk desktop.
  • easeelastic.out jadi power3.out dan durasi ×1,25, sesuai saran "Fit this site". Redaman pegas 11 → 13 supaya pantulannya lebih tenang.
  • Tailwindweb contoh tidak memakai Tailwind. Dipasang v4 tanpa preflight (hanya theme + utilities) agar gaya lama tetap.
components/MagneticButton.jsx · terpasang di web contoh
/*
 * Magnetic Button — diadaptasi untuk Tandarupa
 * Asal: https://asset.inwebsite.online/snippets/micro-motion/magnetic-button (v1.0.1)
 * Perubahan: dipakai untuk tombol CTA hero (tautan <a> lama tetap), warna aksen #e8461e,
 * tanpa kartu/judul/kursor kustom/self-demo, ease elastic → power3.out, efek hanya aktif
 * di perangkat dengan mouse atau trackpad. Di layar sentuh tombol tetap tombol biasa.
 */
import { useRef } from 'react'
import gsap from 'gsap'
import { useGSAP } from '@gsap/react'

gsap.registerPlugin(useGSAP)

// Magnet hanya untuk pointer yang bisa melayang (mouse, trackpad). HP dan tablet: tombol biasa.
const FINE_POINTER = '(hover: hover) and (pointer: fine)'

const VARIANT_CLASS = {
  accent:
    'border-transparent bg-[#e8461e] text-[#f1ede4] shadow-[0_14px_30px_-14px_rgba(232,70,30,0.85)] hover:bg-[#141311] hover:shadow-[0_14px_30px_-14px_rgba(20,19,17,0.6)] focus-visible:ring-[#e8461e]/35',
  line: 'border-current bg-transparent text-[#141311] hover:border-[#141311] hover:bg-[#141311] hover:text-[#f1ede4] focus-visible:ring-[#141311]/25',
}

export default function MagneticButton({ items, strength = 0.42, reach = 72, className = '' }) {
  const root = useRef(null)

  const { contextSafe } = useGSAP(
    () => {
      const el = root.current
      // Layar sentuh: tidak ada listener, tidak ada transform. Tap langsung ke onClick.
      if (!el || !window.matchMedia(FINE_POINTER).matches) return
      const zones = gsap.utils.toArray('.mgb-zone', el)
      const buttons = gsap.utils.toArray('.mgb-btn', el)
      const labels = gsap.utils.toArray('.mgb-label', el)

      // Zona pembungkus tidak ikut bergerak, jadi kotaknya = posisi diam tombol (koordinat layar).
      let boxes = []
      const measure = () => {
        boxes = zones.map((z) => {
          const b = z.getBoundingClientRect()
          return { cx: b.left + b.width / 2, cy: b.top + b.height / 2, w: b.width, h: b.height }
        })
      }
      measure()

      const pointer = { x: 0, y: 0, active: false }
      const bodies = buttons.map(() => ({ x: 0, y: 0, vx: 0, vy: 0, lx: 0, ly: 0, lvx: 0, lvy: 0, s: 1, vs: 0 }))
      const setBtn = buttons.map((b) => ({ x: gsap.quickSetter(b, 'x', 'px'), y: gsap.quickSetter(b, 'y', 'px'), s: gsap.quickSetter(b, 'scale') }))
      const setLbl = labels.map((l) => ({ x: gsap.quickSetter(l, 'x', 'px'), y: gsap.quickSetter(l, 'y', 'px') }))

      // Fisika pegas di ticker GSAP: tertarik ke kursor, kembali saat kursor menjauh.
      const tick = (_t, deltaMs) => {
        const dt = Math.min(deltaMs, 40) / 1000
        // Hanya tombol terdekat dalam jangkauan yang ditarik, supaya tombol bersebelahan tidak bertabrakan.
        let nearest = -1
        let nearestD = reach
        const dist = boxes.map((box) => {
          if (!pointer.active) return Infinity
          const ex = Math.max(0, Math.abs(pointer.x - box.cx) - box.w / 2)
          const ey = Math.max(0, Math.abs(pointer.y - box.cy) - box.h / 2)
          return Math.hypot(ex, ey)
        })
        dist.forEach((d, i) => {
          if (d < nearestD) {
            nearestD = d
            nearest = i
          }
        })
        bodies.forEach((b, i) => {
          let tx = 0
          let ty = 0
          let hot = 0
          if (i === nearest) {
            const box = boxes[i]
            const f = 1 - dist[i] / reach
            hot = f * f * (3 - 2 * f)
            tx = gsap.utils.clamp(-22, 22, (pointer.x - box.cx) * strength * hot)
            ty = gsap.utils.clamp(-14, 14, (pointer.y - box.cy) * strength * hot)
          }
          // Diam dan tidak ada tarikan: lewati, tidak perlu menulis transform tiap frame.
          if (!hot && Math.abs(b.x) + Math.abs(b.y) + Math.abs(b.vx) + Math.abs(b.vy) + Math.abs(b.s - 1) + Math.abs(b.vs) < 0.01) return
          // Tombol: pegas yang sedikit lewat saat dilepas (redaman 13, aslinya 11, supaya lebih tenang).
          b.vx += (200 * (tx - b.x) - 13 * b.vx) * dt
          b.vy += (200 * (ty - b.y) - 13 * b.vy) * dt
          b.x += b.vx * dt
          b.y += b.vy * dt
          // Label bergerak sedikit lebih jauh dari tombolnya (paralaks).
          b.lvx += (260 * (tx * 0.32 - b.lx) - 13 * b.lvx) * dt
          b.lvy += (260 * (ty * 0.32 - b.ly) - 13 * b.lvy) * dt
          b.lx += b.lvx * dt
          b.ly += b.lvy * dt
          b.vs += (300 * (1 + hot * 0.06 - b.s) - 16 * b.vs) * dt
          b.s += b.vs * dt
          setBtn[i].x(b.x)
          setBtn[i].y(b.y)
          setBtn[i].s(b.s)
          setLbl[i].x(b.lx)
          setLbl[i].y(b.ly)
        })
      }
      gsap.ticker.add(tick)

      // Kursor dibaca di seluruh jendela: tarikan mulai `reach` px sebelum kursor menyentuh tombol.
      const onMove = (e) => {
        if (e.pointerType !== 'mouse') return
        measure()
        pointer.x = e.clientX
        pointer.y = e.clientY
        pointer.active = true
      }
      const onOut = (e) => {
        if (!e.relatedTarget) pointer.active = false
      }
      const onDown = (e) => {
        if (e.pointerType !== 'mouse') return
        const i = buttons.findIndex((b) => b.contains(e.target))
        if (i >= 0) bodies[i].vs -= 2.2
      }

      window.addEventListener('pointermove', onMove, { passive: true })
      window.addEventListener('scroll', measure, { passive: true })
      window.addEventListener('resize', measure)
      document.addEventListener('pointerout', onOut)
      el.addEventListener('pointerdown', onDown)

      return () => {
        gsap.ticker.remove(tick)
        window.removeEventListener('pointermove', onMove)
        window.removeEventListener('scroll', measure)
        window.removeEventListener('resize', measure)
        document.removeEventListener('pointerout', onOut)
        el.removeEventListener('pointerdown', onDown)
      }
    },
    { scope: root, dependencies: [strength, reach, items.length], revertOnUpdate: true },
  )

  // Perilaku klik lama dipanggil dulu, baru label "ditekan" sebentar.
  const click = (i, e) => {
    items[i]?.onClick?.(e)
    const label = e.currentTarget.querySelector('.mgb-label')
    contextSafe(() => {
      gsap.fromTo(label, { scale: 0.88 }, { scale: 1, duration: 0.75, ease: 'power3.out' })
    })()
  }

  return (
    <div ref={root} className={`flex flex-wrap items-center gap-4 ${className}`}>
      {items.map((item, i) => (
        <span key={i} className="mgb-zone relative inline-flex">
          <a
            href={item.href}
            onClick={(e) => click(i, e)}
            className={`mgb-btn group relative inline-flex cursor-pointer items-center rounded-full border-[1.5px] border-solid px-[22px] py-[14px] text-[16px] font-medium whitespace-nowrap no-underline transition-[background-color,color,border-color,box-shadow] duration-300 will-change-transform focus-visible:ring-4 focus-visible:outline-none ${VARIANT_CLASS[item.variant || 'accent']}`}
          >
            <span className="mgb-label inline-flex items-center gap-2.5">
              {item.label}
              {item.arrow && <span aria-hidden="true" className="transition-transform duration-300 group-hover:translate-x-1">→</span>}
            </span>
          </a>
        </span>
      ))}
    </div>
  )
}

Langkah 6 · Bandingkan

Sebelum vs sesudah.

Sebelum

Sesudah

Di layar lebar, kartu kanan hidup: dekatkan kursor ke tombol Ceritakan usahamu. Di HP kartu menampilkan rekamannya, karena layar sentuh tidak punya kursor. Tombol di web aslinya tetap bisa ditekan di HP, hanya tanpa tarikan magnet.

01 · SIAPKANEditor, React, agent, akun (login)
02 · JELAJAHMicro Interactions → Call to action
03 · BUKAPratinjau, install, kode (login)
04 · PASANGSebut tombol, aksi klik, dan aturan HP