hlaman artikel

This commit is contained in:
krizzn65 2026-02-17 17:20:06 +07:00
parent 21065b0ab3
commit c3ba927c79
16 changed files with 2021 additions and 394 deletions

View File

@ -19,5 +19,7 @@
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
"registries": {
"@react-bits": "https://reactbits.dev/r/{name}.json"
}
}

View File

@ -9,9 +9,15 @@
"version": "0.0.0",
"dependencies": {
"@iconify/react": "^6.0.2",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"axios": "^1.13.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^10.18.0",
"lucide-react": "^0.564.0",
"react": "^18.2.0",
@ -937,6 +943,44 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz",
"integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
"integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.4",
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz",
"integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.7.5"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@ -1091,6 +1135,685 @@
"node": ">= 8"
}
},
"node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
"integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
"license": "MIT"
},
"node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
"integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-checkbox": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz",
"integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@radix-ui/react-use-previous": "1.1.1",
"@radix-ui/react-use-size": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
"integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-context": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
"integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
"integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-escape-keydown": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
"integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-scope": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
"integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-id": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-label": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
"integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.4"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
"integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.4"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
"integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.0.0",
"@radix-ui/react-arrow": "1.1.7",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.1",
"@radix-ui/react-use-rect": "1.1.1",
"@radix-ui/react-use-size": "1.1.1",
"@radix-ui/rect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-portal": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-presence": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-primitive": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-select": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
"integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/number": "1.1.1",
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-collection": "1.1.7",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.11",
"@radix-ui/react-focus-guards": "1.1.3",
"@radix-ui/react-focus-scope": "1.1.7",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-popper": "1.2.8",
"@radix-ui/react-portal": "1.1.9",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@radix-ui/react-use-layout-effect": "1.1.1",
"@radix-ui/react-use-previous": "1.1.1",
"@radix-ui/react-visually-hidden": "1.2.3",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.6.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-switch": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
"integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@radix-ui/react-use-previous": "1.1.1",
"@radix-ui/react-use-size": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-effect-event": "0.0.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-effect-event": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-callback-ref": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-previous": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
"integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-rect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
"integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
"license": "MIT",
"dependencies": {
"@radix-ui/rect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-size": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
"integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-visually-hidden": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
"integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/rect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT"
},
"node_modules/@remix-run/router": {
"version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
@ -1506,14 +2229,14 @@
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@ -1524,7 +2247,7 @@
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
@ -1657,6 +2380,18 @@
"dev": true,
"license": "Python-2.0"
},
"node_modules/aria-hidden": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -2075,6 +2810,12 @@
"node": ">=0.4.0"
}
},
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@ -2108,6 +2849,34 @@
"dev": true,
"license": "ISC"
},
"node_modules/embla-carousel": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT"
},
"node_modules/embla-carousel-react": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",
"integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==",
"license": "MIT",
"dependencies": {
"embla-carousel": "8.6.0",
"embla-carousel-reactive-utils": "8.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/embla-carousel-reactive-utils": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
"integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
"license": "MIT",
"peerDependencies": {
"embla-carousel": "8.6.0"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -2688,6 +3457,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-nonce": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@ -3607,6 +4385,53 @@
"node": ">=0.10.0"
}
},
"node_modules/react-remove-scroll": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
"integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
"license": "MIT",
"dependencies": {
"react-remove-scroll-bar": "^2.3.7",
"react-style-singleton": "^2.2.3",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.3",
"use-sidecar": "^1.1.3"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-remove-scroll-bar": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
"license": "MIT",
"dependencies": {
"react-style-singleton": "^2.2.2",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-router": {
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
@ -3639,6 +4464,28 @@
"react-dom": ">=16.8"
}
},
"node_modules/react-style-singleton": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
"license": "MIT",
"dependencies": {
"get-nonce": "^1.0.0",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -4164,6 +5011,49 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-callback-ref": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sidecar": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
"license": "MIT",
"dependencies": {
"detect-node-es": "^1.1.0",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/usehooks-ts": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",

View File

@ -11,9 +11,15 @@
},
"dependencies": {
"@iconify/react": "^6.0.2",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"axios": "^1.13.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^10.18.0",
"lucide-react": "^0.564.0",
"react": "^18.2.0",

View File

@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { Link } from 'react-router-dom';
import { fadeIn, slideUp, staggerChildren } from '../utils/motionVariants';
import { fadeIn, slideUp } from '../utils/motionVariants';
import { Gallery4 } from './gallery4';
const Articles = () => {
const [articles, setArticles] = useState([]);
@ -13,240 +14,126 @@ const Articles = () => {
const mockArticles = [
{
id: 1,
title: 'Pentingnya Menjaga Lingkungan Kampus Aman dari Kekerasan Seksual',
slug: 'pentingnya-menjaga-lingkungan-kampus-aman-dari-kekerasan-seksual',
image: 'articles/safe-campus.jpg',
content: 'Lingkungan kampus yang aman adalah hak setiap sivitas akademika.',
title: 'Guru Besar UGM Diduga Lakukan Kekerasan Seksual, Diberhentikan Sementara',
slug: 'https://www.detik.com/jateng/berita/d-6204001/dugaan-kekerasan-seksual-guru-besar-ugm-dipecat-sebagai-dosen',
image: 'https://images.unsplash.com/photo-1592280771800-45cb10bd3dcf?q=80&w=1740&auto=format&fit=crop',
content: 'Universitas Gadjah Mada (UGM) mengambil tindakan tegas dengan memberhentikan sementara seorang guru besar yang diduga terlibat kasus kekerasan seksual.',
is_published: true,
published_at: '2024-01-07T00:00:00.000000Z'
published_at: '2025-01-20T00:00:00.000000Z'
},
{
id: 2,
title: 'Prosedur Pelaporan Kasus Kekerasan Seksual di Polije',
slug: 'prosedur-pelaporan-kasus-kekerasan-seksual-di-polije',
image: 'articles/reporting-procedure.jpg',
content: 'Prosedur pelaporan kasus kekerasan seksual di Politeknik Negeri Jember.',
title: 'Rektor Universitas Pancasila Nonaktif Jalani Pemeriksaan Kasus Pelecehan',
slug: 'https://metro.tempo.co/read/1840000/kasus-pelecehan-seksual-rektor-universitas-pancasila',
image: 'https://images.unsplash.com/photo-1541339907198-e08756dedf3f?q=80&w=1740&auto=format&fit=crop',
content: 'Polda Metro Jaya memeriksa Rektor Universitas Pancasila nonaktif terkait laporan dugaan pelecehan seksual terhadap pegawai kampus.',
is_published: true,
published_at: '2024-01-05T00:00:00.000000Z'
published_at: '2024-06-15T00:00:00.000000Z'
},
{
id: 3,
title: 'Hak dan Kewajiban Korban dan Pelapor Kekerasan Seksual',
slug: 'hak-dan-kewajiban-korban-dan-pelapor-kekerasan-seksual',
image: 'articles/rights-responsibilities.jpg',
content: 'Sebagai korban atau pelapor kekerasan seksual, Anda memiliki hak-hak.',
title: 'Unand Resmikan Satgas PPK, Perluas Cakupan Penanganan Kekerasan',
slug: 'https://www.unand.ac.id/id/berita-peristiwa/berita/item/5799-resmikan-satgas-ppk-rektor-unand-kawal-kampus-aman.html',
image: 'https://images.unsplash.com/photo-1523050854058-8df90110c9f1?q=80&w=1740&auto=format&fit=crop',
content: 'Universitas Andalas meresmikan Satuan Tugas Pencegahan dan Penanganan Kekerasan (PPK) untuk menciptakan lingkungan kampus yang aman dan inklusif.',
is_published: true,
published_at: '2024-01-03T00:00:00.000000Z'
published_at: '2024-11-10T00:00:00.000000Z'
},
{
id: 4,
title: 'Mahasiswa Unsri Tuntut Penuntasan Kasus Pelecehan Seksual',
slug: 'https://www.cnnindonesia.com/nasional/20211203145209-12-729000/mahasiswa-unsri-demo-tuntut-usut-tuntas-dugaan-pelecehan-seksual',
image: 'https://images.unsplash.com/photo-1555848960-8c3af5e4860c?q=80&w=1740&auto=format&fit=crop',
content: 'Ratusan mahasiswa Universitas Sriwijaya menggelar aksi damai menuntut pengusutan tuntas kasus dugaan pelecehan seksual oleh oknum dosen.',
is_published: true,
published_at: '2024-10-05T00:00:00.000000Z'
},
{
id: 5,
title: 'Kemendikbudristek Cabut Izin Kampus yang Abaikan Kasus Kekerasan Seksual',
slug: 'https://nasional.kompas.com/read/2023/06/07/11261391/izin-23-perguruan-tinggi-dicabut-ada-kampus-yang-abaikan-kasus-kekerasan',
image: 'https://images.unsplash.com/photo-1450101499163-c8848c66ca85?q=80&w=1740&auto=format&fit=crop',
content: 'Kemendikbudristek mengambil langkah tegas mencabut izin operasional perguruan tinggi yang terbukti melakukan pelanggaran berat, termasuk pembiaran kekerasan seksual.',
is_published: true,
published_at: '2024-02-01T00:00:00.000000Z'
},
{
id: 6,
title: 'Puan Maharani: Kampus Harus Jadi Ruang Aman Bebas Kekerasan Seksual',
slug: 'https://www.dpr.go.id/berita/detail/id/35000/t/Ketua+DPR+Minta+Kampus+Jadi+Ruang+Aman+dari+Kekerasan+Seksual',
image: 'https://images.unsplash.com/photo-1557804506-669a67965ba0?q=80&w=1740&auto=format&fit=crop',
content: 'Ketua DPR RI Puan Maharani menegaskan pentingnya komitmen perguruan tinggi dalam menciptakan ruang aman bebas dari segala bentuk kekerasan seksual.',
is_published: true,
published_at: '2024-09-15T00:00:00.000000Z'
},
{
id: 7,
title: 'Komnas Perempuan: Kekerasan Seksual di Lingkungan Pendidikan Masih Mengkhawatirkan',
slug: 'https://www.kompas.id/baca/humaniora/2024/03/07/kekerasan-seksual-di-lingkungan-pendidikan-masih-tinggi',
image: 'https://images.unsplash.com/photo-1573164713988-8665fc963095?q=80&w=1740&auto=format&fit=crop',
content: 'Komnas Perempuan mencatat angka kekerasan seksual di lingkungan pendidikan masih tinggi dan memerlukan penanganan sistemik yang lebih serius.',
is_published: true,
published_at: '2024-03-08T00:00:00.000000Z'
},
{
id: 8,
title: 'Pentingnya Pendidikan Seksual di Kampus untuk Cegah Tindakan Asusila',
slug: 'https://edukasi.kompas.com/read/2021/11/12/100000371/pentingnya-pendidikan-seksual-sejak-dini-untuk-cegah-pelecehan',
image: 'https://images.unsplash.com/photo-1544531586-fde5298cdd40?q=80&w=1740&auto=format&fit=crop',
content: 'Pendidikan seksual yang komprehensif di lingkungan kampus dinilai efektif sebagai langkah preventif untuk mencegah terjadinya tindak asusila.',
is_published: true,
published_at: '2024-01-10T00:00:00.000000Z'
}
];
setArticles(mockArticles);
setLoading(false);
}, []);
const formatDate = (dateString) => {
const options = { year: 'numeric', month: 'long', day: 'numeric' };
return new Date(dateString).toLocaleDateString('id-ID', options);
};
const galleryItems = articles.map(article => ({
id: article.id.toString(),
title: article.title,
description: article.content,
href: article.slug,
image: article.image
}));
const SectionTitle = (
<span>
Artikel & <span className="text-primary">Pengumuman</span>
</span>
);
return (
<section id="articles" className="py-20 bg-white relative overflow-hidden">
{/* Background Pattern */}
<div className="absolute inset-0 opacity-5">
<div className="absolute top-10 right-10 w-64 h-64 bg-primary rounded-full blur-3xl"></div>
<div className="absolute bottom-10 left-10 w-96 h-96 bg-accent rounded-full blur-3xl"></div>
<div id="articles" className="bg-gray-50 dark:bg-gray-900 min-h-screen relative overflow-hidden">
{/* Background Decorations */}
<div className="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none">
<div className="absolute top-0 right-0 w-[500px] h-[500px] bg-primary/5 rounded-full blur-3xl translate-x-1/3 -translate-y-1/3"></div>
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-blue-500/5 rounded-full blur-3xl -translate-x-1/3 translate-y-1/3"></div>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Section Header */}
<motion.div
className="text-center mb-20"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
<motion.h2
className="text-4xl md:text-5xl font-bold text-gray-900 mb-6"
variants={slideUp}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
Artikel & <span className="text-primary">Pengumuman</span>
</motion.h2>
<motion.p
className="text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: 0.2 }}
>
Dapatkan informasi terbaru seputar layanan, edukasi, dan pengumuman penting dari Satgas PPKPT Polije.
</motion.p>
</motion.div>
{/* Loading State */}
<div className="relative z-10">
{loading && (
<motion.div
className="flex justify-center items-center py-20"
variants={fadeIn}
initial="hidden"
animate="visible"
>
<div className="flex flex-col items-center space-y-4">
<div className="w-12 h-12 border-4 border-primary/20 border-t-primary rounded-full animate-spin"></div>
<p className="text-gray-600 font-medium">Memuat artikel...</p>
</div>
</motion.div>
<div className="flex justify-center items-center py-32">
<div className="w-12 h-12 border-4 border-primary/20 border-t-primary rounded-full animate-spin"></div>
</div>
)}
{/* Error State */}
{error && (
<motion.div
className="text-center py-20"
variants={fadeIn}
initial="hidden"
animate="visible"
>
<div className="bg-danger/10 border border-danger/20 rounded-2xl p-8 max-w-md mx-auto">
<div className="w-16 h-16 bg-danger/20 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-danger" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Gagal Memuat Artikel</h3>
<p className="text-gray-600 text-sm mb-4">{error}</p>
<button
onClick={fetchArticles}
className="px-6 py-2 bg-primary text-white rounded-xl hover:bg-primary-dark transition-all duration-300 font-medium"
>
Coba Lagi
</button>
</div>
</motion.div>
<div className="text-center py-32 text-red-500 max-w-lg mx-auto px-4">
<p>{error}</p>
</div>
)}
{/* Articles Grid */}
{!loading && !error && (
<>
{articles.length === 0 ? (
<motion.div
className="text-center py-20"
variants={fadeIn}
initial="hidden"
animate="visible"
>
<div className="bg-gray-50 rounded-2xl p-12 max-w-md mx-auto">
<div className="w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Belum Ada Artikel</h3>
<p className="text-gray-600 text-sm">Belum ada artikel atau pengumuman yang tersedia saat ini.</p>
</div>
</motion.div>
) : (
<motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
variants={staggerChildren}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
{articles.map((article, index) => (
<motion.article
key={article.id || index}
className="bg-white rounded-2xl shadow-soft hover:shadow-card transition-all duration-300 hover:-translate-y-2 overflow-hidden group"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: 0.1 * index }}
>
{/* Article Image */}
<div className="relative h-48 overflow-hidden">
{article.image ? (
<img
src={article.image}
alt={article.title}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
/>
) : (
<div className="w-full h-full bg-gradient-to-br from-primary/10 to-accent/10 flex items-center justify-center">
<svg className="w-16 h-16 text-primary/30" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
</svg>
</div>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</div>
{/* Article Content */}
<div className="p-6 space-y-4">
{/* Date */}
<div className="flex items-center text-sm text-gray-500">
<svg className="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
</svg>
{formatDate(article.published_at)}
</div>
{/* Title */}
<h3 className="text-xl font-bold text-gray-900 line-clamp-2 group-hover:text-primary transition-colors">
{article.title}
</h3>
{/* Excerpt */}
<p className="text-gray-600 text-sm leading-relaxed line-clamp-3">
{article.excerpt}
</p>
{/* Read More Link */}
<div className="pt-4">
<Link
to={`/artikel/${article.slug}`}
className="inline-flex items-center text-primary font-semibold text-sm hover:text-primary-dark transition-colors group"
>
Baca Selengkapnya
<svg className="w-4 h-4 ml-1 transform group-hover:translate-x-1 transition-transform" fill="currentColor" viewBox="0 0 24 24">
<path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/>
</svg>
</Link>
</div>
</div>
</motion.article>
))}
</motion.div>
)}
</>
)}
{/* View All Button */}
{!loading && !error && articles.length > 0 && (
<motion.div
className="text-center mt-12"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: 0.4 }}
>
<Link
to="/artikel"
className="inline-flex items-center px-8 py-4 bg-primary text-white rounded-xl hover:bg-primary-dark transition-all duration-300 hover:shadow-lg hover:-translate-y-1 font-semibold shadow-soft"
>
Lihat Semua Artikel
<svg className="w-5 h-5 ml-2" fill="currentColor" viewBox="0 0 24 24">
<path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/>
</svg>
</Link>
</motion.div>
<Gallery4
title={SectionTitle}
description="Dapatkan informasi terbaru seputar layanan, edukasi, dan pengumuman penting dari Satgas PPKPT Polije. Kami berkomitmen untuk transparansi dan edukasi."
items={galleryItems}
/>
)}
</div>
</section>
</div>
);
};

View File

@ -2,99 +2,119 @@ import React from 'react';
import { Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import { fadeIn, slideUp, staggerChildren } from '../utils/motionVariants';
import {
MessageCircle,
FileText,
Shield,
Users,
HeartHandshake,
Phone,
CheckCircle2,
Search,
Gavel,
Smile
} from 'lucide-react';
import { SpotlightCard } from './ui/spotlight-card';
import { FlowButton } from './ui/flow-button';
const Services = () => {
const reportingMethods = [
{
id: 'whatsapp',
title: 'Via WhatsApp',
description: 'Laporkan secara langsung melalui WhatsApp untuk respons cepat dan konsultasi awal dengan tim kami.',
icon: (
<svg className="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.149-.67.149-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.521.151-.172.2-.296.3-.495.099-.198.05-.372-.025-.521-.075-.148-.669-1.611-.916-2.206-.242-.579-.487-.501-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.709.306 1.263.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.695.248-1.29.173-1.414-.074-.123-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413"/>
</svg>
),
features: ['Respons 24/7', 'Konsultasi awal', 'Bimbingan langkah selanjutnya'],
buttonText: 'Butuh Bantuan Darurat',
buttonColor: 'bg-accent hover:bg-accent-dark',
buttonLink: 'https://wa.me/6281234567890'
description: 'Layanan cepat tanggap untuk konsultasi awal dan pelaporan darurat. Terhubung langsung dengan tim satgas kami.',
icon: <MessageCircle className="w-8 h-8" />,
features: ['Respons 24/7', 'Konsultasi Privat', 'Pendampingan Awal'],
buttonText: 'Chat WhatsApp Sekarang',
buttonColor: 'bg-green-600 hover:bg-green-700',
buttonLink: 'https://wa.me/6281234567890',
gradient: 'from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20',
iconColor: 'text-green-600 dark:text-green-400',
spotlightColor: 'rgba(34, 197, 94, 0.2)' // Green glow for WhatsApp
},
{
id: 'form',
title: 'Form Pengaduan Online',
description: 'Isi form pengaduan secara online dengan detail lengkap dan upload bukti pendukung untuk proses yang lebih terstruktur.',
icon: (
<svg className="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
<path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
<path d="M8 12h8v2H8zm0 4h8v2H8zm0-8h5v2H8z"/>
</svg>
),
features: ['Form terstruktur', 'Upload bukti', 'Tracking status laporan'],
buttonText: 'Laporkan Sekarang',
buttonColor: 'bg-primary hover:bg-primary-dark',
buttonLink: '/artikel'
title: 'Form Pengaduan',
description: 'Saluran resmi untuk pelaporan mendetail. Mendukung lampiran bukti dan kronologi lengkap untuk investigasi.',
icon: <FileText className="w-8 h-8" />,
features: ['Form Terstruktur', 'Upload Bukti Aman', 'Tracking Status'],
buttonText: 'Isi Form Laporan',
buttonColor: 'bg-[#191970] hover:bg-blue-900',
buttonLink: '/artikel',
gradient: 'from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20',
iconColor: 'text-[#191970] dark:text-blue-400',
spotlightColor: 'rgba(139, 92, 246, 0.2)' // Purple glow for Form
}
];
const handlingFlow = [
{
step: '01',
title: 'Pelaporan',
desc: 'Laporan masuk via WA atau Website.',
icon: <FileText className="w-5 h-5" />
},
{
step: '02',
title: 'Verifikasi',
desc: 'Validasi data oleh tim Satgas.',
icon: <CheckCircle2 className="w-5 h-5" />
},
{
step: '03',
title: 'Tindak Lanjut',
desc: 'Investigasi atau mediasi kasus.',
icon: <Search className="w-5 h-5" />
},
{
step: '04',
title: 'Penyelesaian',
desc: 'Pemulihan dan penutupan kasus.',
icon: <Smile className="w-5 h-5" />
}
];
const importantInfo = [
{
icon: '🛡️',
title: 'Aman',
description: 'Identitas Anda akan dirahasiakan sepenuhnya'
icon: <Shield className="w-6 h-6" />,
title: 'Dijamin Aman',
description: 'Identitas pelapor dirahasiakan sepenuhnya sesuai kode etik.'
},
{
icon: '👥',
icon: <Users className="w-6 h-6" />,
title: 'Profesional',
description: 'Ditangani oleh tim yang berpengalaman'
description: 'Ditangani oleh tim ahli yang berpengalaman dan objektif.'
},
{
icon: '💚',
title: 'Support',
description: 'Dapatkan pendampingan penuh dari kami'
icon: <HeartHandshake className="w-6 h-6" />,
title: 'Pendampingan',
description: 'Dukungan psikologis dan hukum selama proses berjalan.'
}
];
return (
<section id="services" className="py-20 bg-gray-50 relative overflow-hidden">
{/* Background Pattern */}
<div className="absolute inset-0 opacity-5">
<div className="absolute top-20 left-20 w-72 h-72 bg-accent rounded-full blur-3xl"></div>
<div className="absolute bottom-20 right-20 w-96 h-96 bg-primary rounded-full blur-3xl"></div>
<section id="services" className="pt-20 pb-24 bg-gray-50 dark:bg-gray-900 relative overflow-hidden transition-colors duration-300">
{/* Background Decorations */}
<div className="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none">
<div className="absolute top-[-10%] right-[-5%] w-96 h-96 bg-purple-200/30 dark:bg-purple-900/10 rounded-full blur-3xl"></div>
<div className="absolute bottom-[-10%] left-[-5%] w-96 h-96 bg-blue-200/30 dark:bg-blue-900/10 rounded-full blur-3xl"></div>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Section Header */}
<motion.div
className="text-center mb-20"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
<motion.h2
className="text-4xl md:text-5xl font-bold text-gray-900 mb-6"
variants={slideUp}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
Cara <span className="text-primary">Melapor</span>
</motion.h2>
<motion.p
className="text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: 0.2 }}
>
Pilih metode pelaporan yang paling nyaman untuk Anda. Kami siap membantu dengan profesionalisme dan kerahasiaan terjamin.
</motion.p>
</motion.div>
{/* Reporting Methods Cards */}
<motion.div
className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-20"
{/* Header Section */}
<div className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-6 tracking-tight">
Cara <span className="text-[#191970] dark:text-blue-400">Melapor</span>
</h2>
<p className="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto leading-relaxed">
Kami menyediakan ruang aman bagi Anda untuk bersuara. Pilih metode yang paling nyaman, kami siap mendampingi setiap langkahnya.
</p>
</div>
{/* Reporting Methods Grid */}
<motion.div
className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-24"
variants={staggerChildren}
initial="hidden"
whileInView="visible"
@ -103,145 +123,175 @@ const Services = () => {
{reportingMethods.map((method, index) => (
<motion.div
key={method.id}
className="bg-white rounded-3xl p-8 shadow-soft hover:shadow-card transition-all duration-500 hover:-translate-y-2 group"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: 0.2 * index }}
whileHover={{ y: -5 }}
className="h-full"
>
{/* Icon */}
<motion.div
className="w-20 h-20 bg-gradient-to-br from-primary/10 to-accent/10 rounded-2xl flex items-center justify-center text-primary mb-6 group-hover:from-primary/20 group-hover:to-accent/20 transition-all duration-300"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ duration: 0.3 }}
<SpotlightCard
className="h-full p-8 rounded-3xl border border-white/20 dark:border-white/10 bg-white/10 dark:bg-black/20 backdrop-blur-sm shadow-xl transition-all duration-300 group hover:bg-white/15 dark:hover:bg-white/5"
spotlightColor={method.spotlightColor}
>
{method.icon}
</motion.div>
{/* Gradient Blob Background */}
<div className={`absolute top-0 right-0 w-64 h-64 bg-gradient-to-br ${method.gradient} rounded-full blur-3xl -z-10 opacity-30 group-hover:opacity-60 transition-opacity duration-500`}></div>
{/* Content */}
<div className="space-y-4">
<h3 className="text-2xl font-bold text-gray-900 group-hover:text-primary transition-colors">
{method.title}
</h3>
<p className="text-gray-600 leading-relaxed">
{method.description}
</p>
{/* Features */}
<div className="space-y-3">
{method.features.map((feature, featureIndex) => (
<div key={featureIndex} className="flex items-center space-x-3">
<div className="w-2 h-2 bg-accent rounded-full flex-shrink-0"></div>
<span className="text-gray-700 text-sm font-medium">{feature}</span>
<div className="flex flex-col h-full justify-between relative z-10">
<div>
<div className={`w-16 h-16 rounded-2xl bg-white dark:bg-gray-700 shadow-sm flex items-center justify-center mb-6 ${method.iconColor}`}>
{method.icon}
</div>
))}
</div>
{/* Button */}
<div className="pt-6">
{method.buttonLink.startsWith('http') ? (
<a
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">
{method.title}
</h3>
<p className="text-gray-600 dark:text-gray-300 mb-6 leading-relaxed">
{method.description}
</p>
<ul className="space-y-3 mb-8">
{method.features.map((feature, i) => (
<li key={i} className="flex items-center text-gray-700 dark:text-gray-300 text-sm font-medium">
<CheckCircle2 className={`w-4 h-4 mr-2 ${method.iconColor}`} />
{feature}
</li>
))}
</ul>
</div>
<div className="w-full flex justify-center mt-auto pt-6">
<FlowButton
text={method.buttonText}
href={method.buttonLink}
target="_blank"
rel="noopener noreferrer"
className={`inline-flex items-center justify-center px-8 py-4 ${method.buttonColor} text-white rounded-xl transition-all duration-300 hover:shadow-lg hover:-translate-y-1 font-semibold w-full sm:w-auto shadow-soft`}
>
{method.buttonText}
</a>
) : (
<Link
to={method.buttonLink}
className={`inline-flex items-center justify-center px-8 py-4 ${method.buttonColor} text-white rounded-xl transition-all duration-300 hover:shadow-lg hover:-translate-y-1 font-semibold w-full sm:w-auto shadow-soft`}
>
{method.buttonText}
</Link>
)}
target={method.buttonLink.startsWith('http') ? "_blank" : undefined}
colorStr={method.id === 'whatsapp' ? '#16a34a' : '#2563eb'}
hoverColorStr={method.id === 'whatsapp' ? '#16a34a' : '#1e40af'}
className="w-full max-w-[280px]"
/>
</div>
</div>
</div>
</SpotlightCard>
</motion.div>
))}
</motion.div>
{/* Important Information */}
<motion.div
className="bg-gradient-to-r from-purple-50 to-purple-100 rounded-3xl p-8 md:p-12"
{/* Support & Flow Section */}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
{/* Trust Indicators (Left) */}
<motion.div
className="xl:col-span-1 bg-white dark:bg-gray-800 rounded-3xl p-8 border border-gray-100 dark:border-gray-700 shadow-md h-full"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-6">Kenapa Kami?</h3>
<div className="space-y-6">
{importantInfo.map((info, index) => (
<div key={index} className="flex items-start space-x-4">
<div className="w-10 h-10 rounded-full bg-blue-50 dark:bg-blue-900/30 flex items-center justify-center text-[#191970] dark:text-blue-300 flex-shrink-0">
{info.icon}
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white text-sm">{info.title}</h4>
<p className="text-gray-500 dark:text-gray-400 text-xs leading-relaxed mt-1">{info.description}</p>
</div>
</div>
))}
</div>
</motion.div>
{/* Process Flow (Right - Timeline) */}
<SpotlightCard
className="xl:col-span-2 relative overflow-hidden rounded-3xl p-8 md:p-10 border border-white/20 dark:border-white/10 bg-white/10 dark:bg-black/20 backdrop-blur-sm shadow-xl transition-all duration-300 group hover:bg-white/15 dark:hover:bg-white/5"
spotlightColor="rgba(25, 25, 112, 0.1)"
>
{/* Background Pattern */}
<div className="absolute top-0 right-0 w-64 h-64 bg-blue-50 dark:bg-blue-900/10 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2"></div>
<div className="absolute bottom-0 left-0 w-48 h-48 bg-purple-50 dark:bg-purple-900/10 rounded-full blur-3xl translate-y-1/2 -translate-x-1/2"></div>
<h3 className="text-2xl font-bold mb-12 relative z-10 text-gray-900 dark:text-white">Alur Penanganan</h3>
<div className="relative z-10 w-full">
{/* Progress Line Background */}
<div className="hidden md:block absolute top-[28px] left-0 w-full h-[2px] bg-gray-200/50 dark:bg-gray-700/50 rounded-full"></div>
{/* Animated Progress Line */}
<motion.div
className="hidden md:block absolute top-[28px] left-0 h-[2px] bg-[#191970] dark:bg-blue-500 origin-left z-0 rounded-full overflow-hidden"
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
viewport={{ once: true }}
transition={{ duration: 1.5, ease: "easeInOut", delay: 0.2 }}
>
{/* Continuous Shimmer Animation */}
<motion.div
className="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white/50 to-transparent"
animate={{ x: ['-100%', '100%'] }}
transition={{
repeat: Infinity,
duration: 2,
ease: "linear",
repeatDelay: 0.5
}}
/>
</motion.div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-8">
{handlingFlow.map((step, index) => (
<motion.div
key={index}
className="relative group/step pt-4 md:pt-0"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: index * 0.2 + 0.3 }}
>
<div className="relative z-10 flex flex-col items-center text-center">
<div className="w-14 h-14 rounded-full bg-white dark:bg-gray-800 border-2 border-gray-100 dark:border-gray-700 flex items-center justify-center mb-4 text-[#191970] dark:text-blue-500 shadow-md group-hover/step:scale-110 group-hover/step:border-[#191970] dark:group-hover/step:border-blue-500 transition-all duration-300 relative bg-opacity-80 backdrop-blur-md">
{step.icon}
</div>
<span className="text-xs font-bold font-mono text-[#191970] dark:text-blue-400 mb-2 opacity-80">{step.step}</span>
<h4 className="font-bold text-gray-900 dark:text-white text-base mb-2">{step.title}</h4>
<p className="text-sm text-gray-700 dark:text-gray-300 leading-relaxed font-medium">{step.desc}</p>
</div>
</motion.div>
))}
</div>
</div>
</SpotlightCard>
</div>
{/* Emergency Banner */}
<motion.div
className="mt-12 bg-red-50 dark:bg-red-900/10 border border-red-100 dark:border-red-900/30 rounded-2xl p-6 flex flex-col md:flex-row items-center justify-between gap-6"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: 0.4 }}
>
<div className="text-center mb-8">
<h3 className="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-4">
Penting Untuk Diketahui
</h3>
<p className="text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
Kami memastikan setiap proses pelaporan berjalan dengan aman, profesional, dan mendukung.
</p>
</div>
<motion.div
className="grid grid-cols-1 md:grid-cols-3 gap-6"
variants={staggerChildren}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
{importantInfo.map((info, index) => (
<motion.div
key={index}
className="text-center p-6 bg-white rounded-2xl shadow-soft hover:shadow-card transition-all duration-300"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: 0.1 * index }}
whileHover={{ scale: 1.05 }}
>
<div className="text-4xl mb-4">{info.icon}</div>
<h4 className="text-lg font-bold text-gray-900 mb-2">{info.title}</h4>
<p className="text-gray-600 text-sm leading-relaxed">{info.description}</p>
</motion.div>
))}
</motion.div>
</motion.div>
{/* Emergency Notice */}
<motion.div
className="mt-12 bg-gradient-to-r from-danger/10 to-danger/5 border border-danger/20 rounded-2xl p-6"
variants={fadeIn}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ delay: 0.6 }}
>
<div className="flex items-center space-x-4">
<div className="flex-shrink-0">
<div className="w-12 h-12 bg-danger/20 rounded-xl flex items-center justify-center">
<svg className="w-6 h-6 text-danger" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
</svg>
</div>
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center text-red-600 dark:text-red-400">
<Phone className="w-6 h-6" />
</div>
<div className="flex-1">
<h4 className="text-lg font-semibold text-gray-900 mb-1">Darurat? Hubungi Kami Sekarang</h4>
<p className="text-gray-600 text-sm">
Jika Anda atau orang lain berada dalam situasi darurat, segera hubungi WhatsApp kami untuk respons cepat 24/7.
</p>
</div>
<div className="flex-shrink-0">
<a
href="https://wa.me/6281234567890"
target="_blank"
rel="noopener noreferrer"
className="px-6 py-3 bg-danger text-white rounded-xl hover:bg-danger-dark transition-all duration-300 font-semibold shadow-soft hover:shadow-lg"
>
Hubungi Darurat
</a>
<div>
<h4 className="font-bold text-gray-900 dark:text-white">Butuh Bantuan Darurat?</h4>
<p className="text-sm text-gray-600 dark:text-gray-300">Jangan ragu untuk menghubungi kami jika situasi mendesak.</p>
</div>
</div>
<FlowButton
text="Hubungi Sekarang"
href="https://wa.me/6281234567890"
target="_blank"
colorStr="#dc2626"
hoverColorStr="#dc2626"
className="w-auto px-8"
/>
</motion.div>
</div>
</section>
);

View File

@ -0,0 +1,203 @@
"use client";;
import { ArrowLeft, ArrowRight } from "lucide-react";
import { Link } from "react-router-dom";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel";
const data = [
{
id: "shadcn-ui",
title: "shadcn/ui: Building a Modern Component Library",
description:
"Explore how shadcn/ui revolutionized React component libraries by providing a unique approach to component distribution and customization, making it easier for developers to build beautiful, accessible applications.",
href: "https://ui.shadcn.com",
image:
"https://images.unsplash.com/photo-1551250928-243dc937c49d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w2NDI3NzN8MHwxfGFsbHwxMjN8fHx8fHwyfHwxNzIzODA2OTM5fA&ixlib=rb-4.0.3&q=80&w=1080",
},
{
id: "tailwind",
title: "Tailwind CSS: The Utility-First Revolution",
description:
"Discover how Tailwind CSS transformed the way developers style their applications, offering a utility-first approach that speeds up development while maintaining complete design flexibility.",
href: "https://tailwindcss.com",
image:
"https://images.unsplash.com/photo-1551250928-e4a05afaed1e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w2NDI3NzN8MHwxfGFsbHwxMjR8fHx8fHwyfHwxNzIzODA2OTM5fA&ixlib=rb-4.0.3&q=80&w=1080",
},
{
id: "astro",
title: "Astro: The All-in-One Web Framework",
description:
"Learn how Astro's innovative 'Islands Architecture' and zero-JS-by-default approach is helping developers build faster websites while maintaining rich interactivity where needed.",
href: "https://astro.build",
image:
"https://images.unsplash.com/photo-1536735561749-fc87494598cb?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w2NDI3NzN8MHwxfGFsbHwxNzd8fHx8fHwyfHwxNzIzNjM0NDc0fA&ixlib=rb-4.0.3&q=80&w=1080",
},
{
id: "react",
title: "React: Pioneering Component-Based UI",
description:
"See how React continues to shape modern web development with its component-based architecture, enabling developers to build complex user interfaces with reusable, maintainable code.",
href: "https://react.dev",
image:
"https://images.unsplash.com/photo-1548324215-9133768e4094?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w2NDI3NzN8MHwxfGFsbHwxMzF8fHx8fHwyfHwxNzIzNDM1MzA1fA&ixlib=rb-4.0.3&q=80&w=1080",
},
{
id: "nextjs",
title: "Next.js: The React Framework for Production",
description:
"Explore how Next.js has become the go-to framework for building full-stack React applications, offering features like server components, file-based routing, and automatic optimization.",
href: "https://nextjs.org",
image:
"https://images.unsplash.com/photo-1550070881-a5d71eda5800?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w2NDI3NzN8MHwxfGFsbHwxMjV8fHx8fHwyfHwxNzIzNDM1Mjk4fA&ixlib=rb-4.0.3&q=80&w=1080",
},
];
const Gallery4 = ({
title = "Case Studies",
description = "Discover how leading companies and developers are leveraging modern web technologies to build exceptional digital experiences. These case studies showcase real-world applications and success stories.",
items = data
}) => {
const [carouselApi, setCarouselApi] = useState();
const [canScrollPrev, setCanScrollPrev] = useState(false);
const [canScrollNext, setCanScrollNext] = useState(false);
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (!carouselApi) {
return;
}
const updateSelection = () => {
setCanScrollPrev(carouselApi.canScrollPrev());
setCanScrollNext(carouselApi.canScrollNext());
setCurrentSlide(carouselApi.selectedScrollSnap());
};
updateSelection();
carouselApi.on("select", updateSelection);
return () => {
carouselApi.off("select", updateSelection);
};
}, [carouselApi]);
return (
<section className="py-32">
<div className="container mx-auto">
<div className="mb-8 flex items-end justify-between md:mb-14 lg:mb-16">
<div className="flex flex-col gap-4">
<h2 className="text-3xl font-medium md:text-4xl lg:text-5xl">
{title}
</h2>
<p className="max-w-lg text-muted-foreground">{description}</p>
</div>
<div className="hidden shrink-0 gap-2 md:flex">
<Button
size="icon"
variant="ghost"
onClick={() => {
carouselApi?.scrollPrev();
}}
disabled={!canScrollPrev}
className="disabled:pointer-events-auto">
<ArrowLeft className="size-5" />
</Button>
<Button
size="icon"
variant="ghost"
onClick={() => {
carouselApi?.scrollNext();
}}
disabled={!canScrollNext}
className="disabled:pointer-events-auto">
<ArrowRight className="size-5" />
</Button>
</div>
</div>
</div>
<div className="w-full">
<Carousel
setApi={setCarouselApi}
opts={{
align: "start",
loop: true,
breakpoints: {
"(max-width: 768px)": {
dragFree: true,
},
},
}}>
<CarouselContent
className="ml-0 2xl:ml-[max(8rem,calc(50vw-700px))] 2xl:mr-[max(0rem,calc(50vw-700px))]">
{items.map((item) => (
<CarouselItem key={item.id} className="max-w-[320px] pl-[20px] lg:max-w-[360px]">
{item.href.startsWith('/') ? (
<Link to={item.href} className="group rounded-xl">
<div
className="group relative h-full min-h-[27rem] max-w-full overflow-hidden rounded-xl md:aspect-[5/4] lg:aspect-[16/9]">
<img
src={item.image}
alt={item.title}
className="absolute h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-105" />
<div
className="absolute inset-0 h-full bg-[linear-gradient(hsl(var(--primary)/0),hsl(var(--primary)/0.4),hsl(var(--primary)/0.8)_100%)] mix-blend-multiply" />
<div
className="absolute inset-x-0 bottom-0 flex flex-col items-start p-6 text-primary-foreground md:p-8">
<div className="mb-2 pt-4 text-xl font-semibold md:mb-3 md:pt-4 lg:pt-4">
{item.title}
</div>
<div className="mb-8 line-clamp-2 md:mb-12 lg:mb-9">
{item.description}
</div>
<div className="flex items-center text-sm">
Read more{" "}
<ArrowRight className="ml-2 size-5 transition-transform group-hover:translate-x-1" />
</div>
</div>
</div>
</Link>
) : (
<a href={item.href} target="_blank" rel="noopener noreferrer" className="group rounded-xl">
<div
className="group relative h-full min-h-[27rem] max-w-full overflow-hidden rounded-xl md:aspect-[5/4] lg:aspect-[16/9]">
<img
src={item.image}
alt={item.title}
className="absolute h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-105" />
<div
className="absolute inset-0 h-full bg-[linear-gradient(hsl(var(--primary)/0),hsl(var(--primary)/0.4),hsl(var(--primary)/0.8)_100%)] mix-blend-multiply" />
<div
className="absolute inset-x-0 bottom-0 flex flex-col items-start p-6 text-primary-foreground md:p-8">
<div className="mb-2 pt-4 text-xl font-semibold md:mb-3 md:pt-4 lg:pt-4">
{item.title}
</div>
<div className="mb-8 line-clamp-2 md:mb-12 lg:mb-9">
{item.description}
</div>
<div className="flex items-center text-sm">
Read more{" "}
<ArrowRight className="ml-2 size-5 transition-transform group-hover:translate-x-1" />
</div>
</div>
</div>
</a>
)}
</CarouselItem>
))}
</CarouselContent>
</Carousel>
<div className="mt-8 flex justify-center gap-2">
{items.map((_, index) => (
<button
key={index}
className={`h-2 w-2 rounded-full transition-colors ${currentSlide === index ? "bg-primary" : "bg-primary/20"
}`}
onClick={() => carouselApi?.scrollTo(index)}
aria-label={`Go to slide ${index + 1}`} />
))}
</div>
</div>
</section>
);
};
export { Gallery4 };

View File

@ -0,0 +1,47 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props} />
);
})
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@ -0,0 +1,50 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
{...props} />
))
Card.displayName = "Card"
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props} />
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
{...props} />
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props} />
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props} />
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@ -0,0 +1,193 @@
import * as React from "react"
import useEmblaCarousel from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
const CarouselContext = React.createContext(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
const Carousel = React.forwardRef((
{
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
},
ref,
) => {
const [carouselRef, api] = useEmblaCarousel({
...opts,
axis: orientation === "horizontal" ? "x" : "y",
}, plugins)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api) => {
if (!api) {
return
}
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback((event) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
}, [scrollPrev, scrollNext])
React.useEffect(() => {
if (!api || !setApi) {
return
}
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) {
return
}
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
};
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
{...props}>
{children}
</div>
</CarouselContext.Provider>
);
})
Carousel.displayName = "Carousel"
const CarouselContent = React.forwardRef(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel()
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props} />
</div>
);
})
CarouselContent.displayName = "CarouselContent"
const CarouselItem = React.forwardRef(({ className, ...props }, ref) => {
const { orientation } = useCarousel()
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props} />
);
})
CarouselItem.displayName = "CarouselItem"
const CarouselPrevious = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn("absolute h-8 w-8 rounded-full", orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90", className)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
);
})
CarouselPrevious.displayName = "CarouselPrevious"
const CarouselNext = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn("absolute h-8 w-8 rounded-full", orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", className)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
);
})
CarouselNext.displayName = "CarouselNext"
export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext };

View File

@ -0,0 +1,24 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}>
<CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }

View File

@ -0,0 +1,53 @@
// This is file of your component
// You can use any dependencies from npm; we import them automatically in package.json
'use client';
import { ArrowRight } from 'lucide-react';
export function FlowButton({
text = "Button",
href,
target,
rel,
className = "",
colorStr = "#111111", // Default text/border color
hoverColorStr = "#111111", // Circle bg color on hover
}) {
const Component = href ? 'a' : 'button';
return (
<Component
href={href}
target={target}
rel={rel}
className={`group relative flex items-center justify-center gap-1 overflow-hidden rounded-[100px] border-[1.5px] bg-transparent px-8 py-3 text-sm font-semibold cursor-pointer transition-all duration-[600ms] ease-[cubic-bezier(0.23,1,0.32,1)] hover:border-transparent hover:!text-white hover:rounded-[12px] active:scale-[0.95] ${className}`}
style={{
borderColor: `${colorStr}40`, // 40 is hex opacity
color: colorStr
}}
>
{/* Left arrow */}
<ArrowRight
className="absolute w-4 h-4 left-[-25%] fill-none z-[9] group-hover:left-4 group-hover:!stroke-white transition-all duration-[800ms] ease-[cubic-bezier(0.34,1.56,0.64,1)]"
style={{ stroke: colorStr }}
/>
{/* Text */}
<span className="relative z-[1] -translate-x-3 group-hover:translate-x-3 transition-all duration-[800ms] ease-out group-hover:!text-white">
{text}
</span>
{/* Circle Hover Effect */}
<span
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-4 h-4 rounded-[50%] opacity-0 group-hover:w-[300px] group-hover:h-[300px] group-hover:opacity-100 transition-all duration-[800ms] ease-[cubic-bezier(0.19,1,0.22,1)]"
style={{ backgroundColor: hoverColorStr }}
></span>
{/* Right arrow */}
<ArrowRight
className="absolute w-4 h-4 right-4 fill-none z-[9] group-hover:right-[-25%] group-hover:!stroke-white transition-all duration-[800ms] ease-[cubic-bezier(0.34,1.56,0.64,1)]"
style={{ stroke: colorStr }}
/>
</Component>
);
}

View File

@ -0,0 +1,19 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props} />
);
})
Input.displayName = "Input"
export { Input }

View File

@ -0,0 +1,16 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef(({ className, ...props }, ref) => (
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@ -0,0 +1,120 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn("p-1", position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]")}>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props} />
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} />
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

View File

@ -0,0 +1,43 @@
import React, { useRef, useState } from "react";
export const SpotlightCard = ({ children, className = "", spotlightColor = "rgba(255, 255, 255, 0.25)" }) => {
const divRef = useRef(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [opacity, setOpacity] = useState(0);
const handleMouseMove = (e) => {
if (!divRef.current) return;
const rect = divRef.current.getBoundingClientRect();
setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
};
const handleMouseEnter = () => {
setOpacity(1);
};
const handleMouseLeave = () => {
setOpacity(0);
};
return (
<div
ref={divRef}
onMouseMove={handleMouseMove}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={`relative overflow-hidden rounded-3xl border border-gray-100 dark:border-gray-800 shadow-lg ${className}`}
>
<div
className="pointer-events-none absolute -inset-px transition duration-300"
style={{
opacity,
background: `radial-gradient(600px circle at ${position.x}px ${position.y}px, ${spotlightColor}, transparent 40%)`,
}}
/>
<div className="relative h-full">
{children}
</div>
</div>
);
};

View File

@ -0,0 +1,24 @@
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
)} />
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }