Initial commit

This commit is contained in:
ekastn15 2025-06-19 19:59:42 +07:00
commit 3ea52e4b54
36 changed files with 3517 additions and 0 deletions

21
public/.htaccess Normal file
View File

@ -0,0 +1,21 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

View File

@ -0,0 +1,555 @@
/*===== GOOGLE FONTS =====*/
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap");
/*===== VARIABLES CSS =====*/
:root {
--header-height: 3rem;
--font-semi: 600;
/*===== Colores =====*/
/*Purple 260 - Red 355 - Blue 224 - Pink 340*/
/* HSL color mode */
--hue-color: 199;
--first-color: hsl(var(--hue-color), 92%, 83%);
--second-color: hsl(var(--hue-color), 56%, 12%);
/*===== Fuente y tipografia =====*/
--body-font: "Poppins", sans-serif;
--big-font-size: 2rem;
--h2-font-size: 1.25rem;
--normal-font-size: .938rem;
--smaller-font-size: .75rem;
/*===== Margenes =====*/
--mb-2: 1rem;
--mb-4: 2rem;
--mb-5: 2.5rem;
--mb-6: 3rem;
/*===== z index =====*/
--z-back: -10;
--z-fixed: 100;
}
@media screen and (min-width: 968px) {
:root {
--big-font-size: 3.5rem;
--h2-font-size: 2rem;
--normal-font-size: 1rem;
--smaller-font-size: .875rem;
}
}
/*===== BASE =====*/
*, ::before, ::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: var(--header-height) 0 0 0;
font-family: var(--body-font);
font-size: var(--normal-font-size);
color: var(--second-color);
}
h1, h2, p {
margin: 0;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
a {
text-decoration: none;
}
img {
max-width: 100%;
height: auto;
display: block;
}
.tutor__video {
width: 100%;
max-width: 300px; /* Atur ukuran maksimal */
border-radius: 10px;
border: 2px solid #333;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
}
.video-container {
display: flex;
justify-content: center;
align-items: center;
height: 70vh;
width: 100%;
}
#video-webcam {
width: 100%;
max-width: 600px; /* Maksimal ukuran video */
border-radius: 5px;
border: 3px solid #9191e2;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
}
/*===== CLASS CSS ===== */
.section-title {
position: relative;
font-size: var(--h2-font-size);
color: #001f3f;
margin-top: var(--mb-5);
margin-bottom: var(--mb-1);
text-align: center;
}
.section-title::after {
position: absolute;
content: "";
width: 64px;
height: 0.18rem;
left: 0;
right: 0;
margin: auto;
top: 2rem;
background-color: var(--first-color);
}
.section {
padding-top: 3rem;
padding-bottom: 2rem;
}
/*===== LAYOUT =====*/
.bd-grid {
max-width: 1024px;
display: grid;
margin-left: var(--mb-2);
margin-right: var(--mb-2);
}
.l-header {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: var(--z-fixed);
background-color: #fff;
box-shadow: 0 1px 4px rgba(146, 161, 176, 0.15);
}
/*===== NAV =====*/
.nav {
height: var(--header-height);
display: flex;
justify-content: space-between;
align-items: center;
font-weight: var(--font-semi);
}
@media screen and (max-width: 767px) {
.nav__menu {
position: fixed;
top: var(--header-height);
right: -100%;
width: 80%;
height: 100%;
padding: 2rem;
background-color: var(--second-color);
transition: 0.5s;
}
}
.nav__item {
margin-bottom: var(--mb-4);
}
.nav__link {
position: relative;
color: #fff;
}
.nav__link:hover {
position: relative;
}
.nav__link:hover::after {
position: absolute;
content: "";
width: 100%;
height: 0.18rem;
left: 0;
top: 2rem;
background-color: var(--first-color);
}
.nav__logo {
display: flex;
align-items: center;
gap: 10px;
color: var(--second-color);
font-weight: bold;
font-size: 1.2rem;
}
.logo-img {
height: 40px;
width: auto;
}
.nav__toggle {
color: var(--second-color);
font-size: 1.5rem;
cursor: pointer;
}
/*Active menu*/
.active-link::after {
position: absolute;
content: "";
width: 100%;
height: 0.18rem;
left: 0;
top: 2rem;
background-color: var(--first-color);
}
/*=== Show menu ===*/
.show {
right: 0;
}
/*===== HOME =====*/
.home {
position: relative;
row-gap: 5rem;
padding: 4rem 0 5rem;
}
.home__data {
align-self: center;
}
.home__title {
font-size: var(--big-font-size);
margin-bottom: var(--mb-5);
}
.home__title-color {
color: var(--first-color);
}
.home__social {
display: flex;
flex-direction: column;
}
.home__social-icon {
width: max-content;
margin-bottom: var(--mb-2);
font-size: 1.5rem;
color: var(--second-color);
}
.home__social-icon:hover {
color: var(--first-color);
}
.home__img {
position: absolute;
right: 0;
bottom: 0;
width: 260px;
}
.home__blob {
fill: var(--first-color);
}
.home__blob-img {
width: 360px;
}
/*BUTTONS*/
.button {
display: inline-block;
background-color: #001f3f;;
color: #fff;
padding: 0.75rem 2.5rem;
font-weight: var(--font-semi);
border-radius: 0.5rem;
transition: 0.3s;
}
.button:hover {
box-shadow: 0px 10px 36px rgba(0, 0, 0, 0.15);
}
.nav__dropdown {
position: relative;
}
.dropdown__menu {
position: absolute;
top: 100%;
left: 0;
background-color: white;
list-style: none;
padding: 0;
margin: 0;
display: none;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
z-index: 10;
}
.nav__dropdown:hover .dropdown__menu {
display: block;
}
.dropdown__link {
display: block;
padding: 10px 20px;
text-decoration: none;
color: #333;
white-space: nowrap;
}
.dropdown__link:hover {
background-color: #90c7fb;
}
/* ===== ABOUT =====*/
.about__container {
row-gap: 2rem;
text-align: center;
}
.about__subtitle {
margin-bottom: var(--mb-2);
}
.about__img {
justify-self: center;
}
.about__img img {
width: 200px;
border-radius: 0.5rem;
}
/* ===== SKILLS =====*/
.skills__container {
row-gap: 2rem;
text-align: center;
}
.skills__subtitle {
margin-bottom: var(--mb-2);
}
.skills__text {
margin-bottom: var(--mb-4);
}
.skills__data {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
font-weight: var(--font-semi);
padding: 0.5rem 1rem;
margin-bottom: var(--mb-4);
border-radius: 0.5rem;
box-shadow: 0px 4px 25px rgba(14, 36, 49, 0.15);
}
.skills__icon {
font-size: 2rem;
margin-right: var(--mb-2);
color: var(--first-color);
}
.skills__names {
display: flex;
align-items: center;
}
.skills__bar {
position: absolute;
left: 0;
bottom: 0;
background-color: var(--first-color);
height: 0.25rem;
border-radius: 0.5rem;
z-index: var(--z-back);
}
.skills__html {
width: 95%;
}
.skills__css {
width: 85%;
}
.skills__js {
width: 65%;
}
.skills__ux {
width: 85%;
}
.skills__img {
border-radius: 0.5rem;
}
/* ===== WORK =====*/
.work__container {
row-gap: 2rem;
}
.work__img {
box-shadow: 0px 4px 25px rgba(14, 36, 49, 0.15);
border-radius: 0.5rem;
overflow: hidden;
}
.work__img img {
transition: 1s;
}
.work__img img:hover {
transform: scale(1.1);
}
/* ===== CONTACT =====*/
.contact__input {
width: 100%;
font-size: var(--normal-font-size);
font-weight: var(--font-semi);
padding: 1rem;
border-radius: 0.5rem;
border: 1.5px solid var(--second-color);
outline: none;
margin-bottom: var(--mb-4);
}
.contact__button {
display: block;
border: none;
outline: none;
font-size: var(--normal-font-size);
cursor: pointer;
margin-left: auto;
}
/* ===== FOOTER =====*/
.footer {
background-color: var(--second-color);
color: #fff;
text-align: center;
font-weight: var(--font-semi);
padding: 2rem 0;
}
.footer__title {
font-size: 2rem;
margin-bottom: var(--mb-4);
}
.footer__social {
margin-bottom: var(--mb-4);
}
.footer__icon {
font-size: 1.5rem;
color: #fff;
margin: 0 var(--mb-2);
}
.footer__copy {
font-size: var(--smaller-font-size);
}
/* ===== MEDIA QUERIES=====*/
@media screen and (max-width: 320px) {
.home {
row-gap: 2rem;
}
.home__img {
width: 200px;
}
}
@media screen and (min-width: 576px) {
.home {
padding: 4rem 0 2rem;
}
.home__social {
padding-top: 0;
padding-bottom: 2.5rem;
flex-direction: row;
align-self: flex-end;
}
.home__social-icon {
margin-bottom: 0;
margin-right: var(--mb-4);
}
.home__img {
width: 300px;
bottom: 25%;
}
.about__container {
grid-template-columns: repeat(2, 1fr);
align-items: center;
text-align: initial;
}
.skills__container {
grid-template-columns: 0.7fr;
justify-content: center;
column-gap: 1rem;
}
.work__container {
grid-template-columns: repeat(2, 1fr);
column-gap: 2rem;
padding-top: 2rem;
}
.contact__form {
width: 360px;
padding-top: 2rem;
}
.contact__container {
justify-items: center;
}
}
@media screen and (min-width: 768px) {
body {
margin: 0;
}
.section {
padding-top: 4rem;
padding-bottom: 3rem;
}
.section-title {
margin-bottom: var(--mb-6);
}
.section-title::after {
width: 80px;
top: 3rem;
}
.nav {
height: calc(var(--header-height) + 1.5rem);
}
.nav__list {
display: flex;
padding-top: 0;
}
.nav__item {
margin-left: var(--mb-6);
margin-bottom: 0;
}
.nav__toggle {
display: none;
}
.nav__link {
color: var(--second-color);
}
.home {
padding: 8rem 0 2rem;
}
.home__img {
width: 400px;
bottom: 10%;
}
.about__container {
padding-top: 2rem;
}
.about__img img {
width: 300px;
}
.skills__container {
grid-template-columns: repeat(2, 1fr);
column-gap: 2rem;
align-items: center;
text-align: initial;
}
.work__container {
grid-template-columns: repeat(3, 1fr);
column-gap: 2rem;
}
}
@media screen and (min-width: 992px) {
.bd-grid {
margin-left: auto;
margin-right: auto;
}
.home {
padding: 10rem 0 2rem;
}
.home__img {
width: 450px;
}
}

BIN
public/assets/img/silat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

57
public/assets/js/main.js Normal file
View File

@ -0,0 +1,57 @@
/*===== MENU SHOW =====*/
const showMenu = (toggleId, navId) =>{
const toggle = document.getElementById(toggleId),
nav = document.getElementById(navId)
if(toggle && nav){
toggle.addEventListener('click', ()=>{
nav.classList.toggle('show')
})
}
}
showMenu('nav-toggle','nav-menu')
/*==================== REMOVE MENU MOBILE ====================*/
const navLink = document.querySelectorAll('.nav__link')
function linkAction(){
const navMenu = document.getElementById('nav-menu')
// When we click on each nav__link, we remove the show-menu class
navMenu.classList.remove('show')
}
navLink.forEach(n => n.addEventListener('click', linkAction))
/*==================== SCROLL SECTIONS ACTIVE LINK ====================*/
const sections = document.querySelectorAll('section[id]')
const scrollActive = () =>{
const scrollDown = window.scrollY
sections.forEach(current =>{
const sectionHeight = current.offsetHeight,
sectionTop = current.offsetTop - 58,
sectionId = current.getAttribute('id'),
sectionsClass = document.querySelector('.nav__menu a[href*=' + sectionId + ']')
if(scrollDown > sectionTop && scrollDown <= sectionTop + sectionHeight){
sectionsClass.classList.add('active-link')
}else{
sectionsClass.classList.remove('active-link')
}
})
}
window.addEventListener('scroll', scrollActive)
/*===== SCROLL REVEAL ANIMATION =====*/
const sr = ScrollReveal({
origin: 'top',
distance: '60px',
duration: 2000,
delay: 200,
// reset: true
});
sr.reveal('.home__data, .about__img, .skills__subtitle, .skills__text',{});
sr.reveal('.home__img, .about__subtitle, .about__text, .skills__img',{delay: 400});
sr.reveal('.home__social-icon',{ interval: 200});
sr.reveal('.skills__data, .work__img, .contact__input',{interval: 200});

View File

@ -0,0 +1,525 @@
/*===== GOOGLE FONTS =====*/
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
/*===== VARIABLES CSS =====*/
:root{
--header-height: 3rem;
--font-semi: 600;
/*===== Colores =====*/
/*Purple 260 - Red 355 - Blue 224 - Pink 340*/
/* HSL color mode */
--hue-color: 224;
--first-color: hsl(var(--hue-color), 89%, 60%);
--second-color: hsl(var(--hue-color), 56%, 12%);
/*===== Fuente y tipografia =====*/
--body-font: 'Poppins', sans-serif;
--big-font-size: 2rem;
--h2-font-size: 1.25rem;
--normal-font-size: .938rem;
--smaller-font-size: .75rem;
/*===== Margenes =====*/
--mb-2: 1rem;
--mb-4: 2rem;
--mb-5: 2.5rem;
--mb-6: 3rem;
/*===== z index =====*/
--z-back: -10;
--z-fixed: 100;
@media screen and (min-width: 968px){
--big-font-size: 3.5rem;
--h2-font-size: 2rem;
--normal-font-size: 1rem;
--smaller-font-size: .875rem;
}
}
/*===== BASE =====*/
*,::before,::after{
box-sizing: border-box;
}
html{
scroll-behavior: smooth;
}
body{
margin: var(--header-height) 0 0 0;
font-family: var(--body-font);
font-size: var(--normal-font-size);
color: var(--second-color);
}
h1,h2,p{
margin: 0;
}
ul{
margin: 0;
padding: 0;
list-style: none;
}
a{
text-decoration: none;
}
img{
max-width: 100%;
height: auto;
display: block;
}
/*===== CLASS CSS ===== */
.section-title{
position: relative;
font-size: var(--h2-font-size);
color: var(--first-color);
margin-top: var(--mb-2);
margin-bottom: var(--mb-4);
text-align: center;
&::after{
position: absolute;
content: '';
width: 64px;
height: 0.18rem;
left: 0;
right: 0;
margin: auto;
top: 2rem;
background-color: var(--first-color);
}
}
.section{
padding-top: 3rem;
padding-bottom: 2rem;
}
/*===== LAYOUT =====*/
.bd-grid{
max-width: 1024px;
display: grid;
margin-left: var(--mb-2);
margin-right: var(--mb-2);
}
.l-header{
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: var(--z-fixed);
background-color: #fff;
box-shadow: 0 1px 4px rgba(146,161,176,.15);
}
/*===== NAV =====*/
.nav{
height: var(--header-height);
display: flex;
justify-content: space-between;
align-items: center;
font-weight: var(--font-semi);
&__menu{
@media screen and (max-width: 767px){
position: fixed;
top: var(--header-height);
right: -100%;
width: 80%;
height: 100%;
padding: 2rem;
background-color: var(--second-color);
transition: .5s;
}
}
&__item{
margin-bottom: var(--mb-4);
}
&__link{
position: relative;
color: #fff;
&:hover{
position: relative;
&::after{
position: absolute;
content: '';
width: 100%;
height: 0.18rem;
left: 0;
top: 2rem;
background-color: var(--first-color);
}
}
}
&__logo{
color: var(--second-color);
}
&__toggle{
color: var(--second-color);
font-size: 1.5rem;
cursor: pointer;
}
}
/*Active menu*/
.active-link::after{
position: absolute;
content: '';
width: 100%;
height: 0.18rem;
left: 0;
top: 2rem;
background-color: var(--first-color);
}
/*=== Show menu ===*/
.show{
right: 0;
}
/*===== HOME =====*/
.home{
position: relative;
row-gap: 5rem;
padding: 4rem 0 5rem;
&__data{
align-self: center;
}
&__title{
font-size: var(--big-font-size);
margin-bottom: var(--mb-5);
&-color{
color: var(--first-color);
}
}
&__social{
display: flex;
flex-direction: column;
&-icon{
width: max-content;
margin-bottom: var(--mb-2);
font-size: 1.5rem;
color: var(--second-color);
&:hover{
color: var(--first-color);
}
}
}
&__img{
position: absolute;
right: 0;
bottom: 0;
width: 260px;
}
&__blob{
fill: var(--first-color);
&-img{
width: 360px;
}
}
}
/*BUTTONS*/
.button{
display: inline-block;
background-color: var(--first-color);
color: #fff;
padding: .75rem 2.5rem;
font-weight: var(--font-semi);
border-radius: .5rem;
transition: .3s;
&:hover{
box-shadow: 0px 10px 36px rgba(0,0,0,.15);
}
}
/* ===== ABOUT =====*/
.about{
&__container{
row-gap: 2rem;
text-align: center;
}
&__subtitle{
margin-bottom: var(--mb-2);
}
&__img{
justify-self: center;
& img{
width: 200px;
border-radius: .5rem;
}
}
}
/* ===== SKILLS =====*/
.skills{
&__container{
row-gap: 2rem;
text-align: center;
}
&__subtitle{
margin-bottom: var(--mb-2);
}
&__text{
margin-bottom: var(--mb-4);
}
&__data{
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
font-weight: var(--font-semi);
padding: .5rem 1rem;
margin-bottom: var(--mb-4);
border-radius: .5rem;
box-shadow: 0px 4px 25px rgba(14, 36, 49, 0.15);
}
&__icon{
font-size: 2rem;
margin-right: var(--mb-2);
color: var(--first-color);
}
&__names{
display: flex;
align-items: center;
}
&__bar{
position: absolute;
left: 0;
bottom: 0;
background-color: var(--first-color);
height: .25rem;
border-radius: .5rem;
z-index: var(--z-back);
}
&__html{
width: 95%;
}
&__css{
width: 85%;
}
&__js{
width: 65%;
}
&__ux{
width: 85%;
}
&__img{
border-radius: .5rem;
}
}
/* ===== WORK =====*/
.work{
&__container{
row-gap: 2rem;
}
&__img{
box-shadow: 0px 4px 25px rgba(14, 36, 49, 0.15);
border-radius: .5rem;
overflow: hidden;
& img{
transition: 1s;
&:hover{
transform: scale(1.1);
}
}
}
}
/* ===== CONTACT =====*/
.contact{
&__input{
width: 100%;
font-size: var(--normal-font-size);
font-weight: var(--font-semi);
padding: 1rem;
border-radius: .5rem;
border: 1.5px solid var(--second-color);
outline: none;
margin-bottom: var(--mb-4);
}
&__button{
display: block;
border: none;
outline: none;
font-size: var(--normal-font-size);
cursor: pointer;
margin-left: auto;
}
}
/* ===== FOOTER =====*/
.footer{
background-color: var(--second-color);
color: #fff;
text-align: center;
font-weight: var(--font-semi);
padding: 2rem 0;
&__title{
font-size: 2rem;
margin-bottom: var(--mb-4);
}
&__social{
margin-bottom: var(--mb-4);
}
&__icon{
font-size: 1.5rem;
color: #fff;
margin: 0 var(--mb-2);
}
&__copy{
font-size: var(--smaller-font-size);
}
}
/* ===== MEDIA QUERIES=====*/
@media screen and (max-width: 320px){
.home{
row-gap: 2rem;
&__img{
width: 200px;
}
}
}
@media screen and (min-width: 576px){
.home{
padding: 4rem 0 2rem;
&__social{
padding-top: 0;
padding-bottom: 2.5rem;
flex-direction: row;
align-self: flex-end;
&-icon{
margin-bottom: 0;
margin-right: var(--mb-4);
}
}
&__img{
width: 300px;
bottom: 25%;
}
}
.about__container{
grid-template-columns: repeat(2,1fr);
align-items: center;
text-align: initial;
}
.skills__container{
grid-template-columns: .7fr;
justify-content: center;
column-gap: 1rem;
}
.work__container{
grid-template-columns: repeat(2,1fr);
column-gap: 2rem;
padding-top: 2rem;
}
.contact{
&__form{
width: 360px;
padding-top: 2rem ;
}
&__container{
justify-items: center;
}
}
}
@media screen and (min-width: 768px){
body{
margin: 0;
}
.section{
padding-top: 4rem;
padding-bottom: 3rem;
}
.section-title{
margin-bottom: var(--mb-6);
&::after{
width: 80px;
top: 3rem;
}
}
.nav{
height: calc(var(--header-height) + 1.5rem);
&__list{
display: flex;
padding-top: 0;
}
&__item{
margin-left: var(--mb-6);
margin-bottom: 0;
}
&__toggle{
display: none;
}
&__link{
color: var(--second-color);
}
}
.home{
padding: 8rem 0 2rem;
&__img{
width: 400px;
bottom: 10%;
}
}
.about{
&__container{
padding-top: 2rem;
}
&__img{
& img{
width: 300px;
}
}
}
.skills__container{
grid-template-columns: repeat(2,1fr);
column-gap: 2rem;
align-items: center;
text-align: initial;
}
.work__container{
grid-template-columns: repeat(3,1fr);
column-gap: 2rem;
}
}
@media screen and (min-width: 992px){
.bd-grid{
margin-left: auto;
margin-right: auto;
}
.home{
padding: 10rem 0 2rem;
&__img{
width: 450px;
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

0
public/favicon.ico Normal file
View File

55
public/index.php Normal file
View File

@ -0,0 +1,55 @@
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
$kernel->terminate($request, $response);

1392
public/models/Training.ipynb Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

1
public/models/model.json Normal file
View File

@ -0,0 +1 @@
{"format": "layers-model", "generatedBy": "keras v2.14.0", "convertedBy": "TensorFlow.js Converter v4.22.0", "modelTopology": {"keras_version": "2.14.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 99], "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 32, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 64, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 128, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 14, "activation": "softmax", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}}, "training_config": {"loss": "categorical_crossentropy", "metrics": [[{"class_name": "MeanMetricWrapper", "config": {"name": "categorical_accuracy", "dtype": "float32", "fn": "categorical_accuracy"}}]], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Custom>Adam", "config": {"name": "Adam", "weight_decay": null, "clipnorm": null, "global_clipnorm": null, "clipvalue": null, "use_ema": false, "ema_momentum": 0.99, "ema_overwrite_frequency": null, "jit_compile": false, "is_legacy_optimizer": false, "learning_rate": 0.0010000000474974513, "beta_1": 0.9, "beta_2": 0.999, "epsilon": 1e-07, "amsgrad": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "dense/kernel", "shape": [99, 32], "dtype": "float32"}, {"name": "dense/bias", "shape": [32], "dtype": "float32"}, {"name": "dense_1/kernel", "shape": [32, 64], "dtype": "float32"}, {"name": "dense_1/bias", "shape": [64], "dtype": "float32"}, {"name": "dense_2/kernel", "shape": [64, 128], "dtype": "float32"}, {"name": "dense_2/bias", "shape": [128], "dtype": "float32"}, {"name": "dense_3/kernel", "shape": [128, 14], "dtype": "float32"}, {"name": "dense_3/bias", "shape": [14], "dtype": "float32"}]}]}

Binary file not shown.

View File

@ -0,0 +1 @@
{"format": "layers-model", "generatedBy": "keras v2.14.0", "convertedBy": "TensorFlow.js Converter v4.22.0", "modelTopology": {"keras_version": "2.14.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 99], "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 32, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 64, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 128, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 14, "activation": "softmax", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}}, "training_config": {"loss": "categorical_crossentropy", "metrics": [[{"class_name": "MeanMetricWrapper", "config": {"name": "categorical_accuracy", "dtype": "float32", "fn": "categorical_accuracy"}}]], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Custom>Adam", "config": {"name": "Adam", "weight_decay": null, "clipnorm": null, "global_clipnorm": null, "clipvalue": null, "use_ema": false, "ema_momentum": 0.99, "ema_overwrite_frequency": null, "jit_compile": false, "is_legacy_optimizer": false, "learning_rate": 0.0010000000474974513, "beta_1": 0.9, "beta_2": 0.999, "epsilon": 1e-07, "amsgrad": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "dense/kernel", "shape": [99, 32], "dtype": "float32"}, {"name": "dense/bias", "shape": [32], "dtype": "float32"}, {"name": "dense_1/kernel", "shape": [32, 64], "dtype": "float32"}, {"name": "dense_1/bias", "shape": [64], "dtype": "float32"}, {"name": "dense_2/kernel", "shape": [64, 128], "dtype": "float32"}, {"name": "dense_2/bias", "shape": [128], "dtype": "float32"}, {"name": "dense_3/kernel", "shape": [128, 14], "dtype": "float32"}, {"name": "dense_3/bias", "shape": [14], "dtype": "float32"}]}]}

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

0
resources/css/app.css Normal file
View File

1
resources/js/app.js Normal file
View File

@ -0,0 +1 @@
import './bootstrap';

32
resources/js/bootstrap.js vendored Normal file
View File

@ -0,0 +1,32 @@
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// import Pusher from 'pusher-js';
// window.Pusher = Pusher;
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
// });

View File

@ -0,0 +1,202 @@
@extends('layouts.app')
@section('title', 'Deteksi Real-Time')
@section('contents')
<section class="about section" id="about">
<h2 class="section-title text-center">Deteksi Pose Jurus 1 Tangan Kosong IPSI</h2>
<div class="about__container" style="display: flex; flex-wrap: wrap; justify-content: center; gap: 20px;">
<div class="video-container" style="position: relative;">
<video id="webcam" width="640" height="480" autoplay playsinline muted></video>
<canvas id="output-canvas" width="640" height="480"
style="position: absolute; top: 0; left: 0;"></canvas>
</div>
<div id="prediction-result" class="prediction-box">
<p>Prediksi: <span id="pose-label">-</span></p>
<p>Probability: <span id="pose-accuracy">-</span></p>
</div>
</div>
</section>
@endsection
@section('script')
<script src="https://code.jquery.com/jquery-3.6.0.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/pose"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils"></script>
<script>
const poseClasses = [
'A1_benar', 'A1_salah','A2_benar', 'A2_salah', 'A3_benar', 'A3_salah',
'A4_benar', 'A4_salah', 'A5_benar', 'A5_salah', 'A6_benar', 'A6_salah',
'A7_benar', 'A7_salah'
];
const videoElement = document.getElementById('webcam');
const canvasElement = document.getElementById('output-canvas');
const ctx = canvasElement.getContext('2d');
const poseLabelElement = document.getElementById('pose-label');
const poseAccuracyElement = document.getElementById('pose-accuracy');
let model = null;
let poseDetector = null;
let camera = null;
async function loadModel() {
try {
model = await tf.loadLayersModel('/models/model.json');
console.log('✅ Model loaded');
} catch (err) {
console.error('❌ Gagal load model:', err);
alert('Model gagal dimuat');
}
}
function setupPose() {
poseDetector = new Pose({
locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/pose/${file}`
});
poseDetector.setOptions({
modelComplexity: 1,
smoothLandmarks: true,
enableSegmentation: false,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5
});
poseDetector.onResults(onPoseResults);
}
function onPoseResults(results) {
if (!results.poseLandmarks) return;
ctx.clearRect(0, 0, canvasElement.width, canvasElement.height);
ctx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
drawLandmarks(results.poseLandmarks);
const landmarks = results.poseLandmarks.map(landmark => [
landmark.x * canvasElement.height,
landmark.y * canvasElement.height,
landmark.z * canvasElement.width
]).flat();
predictPose(landmarks);
}
function drawLandmarks(landmarks) {
drawConnections(landmarks);
ctx.fillStyle = '#FF0000';
landmarks.forEach(landmark => {
ctx.beginPath();
ctx.arc(landmark.x * canvasElement.width, landmark.y * canvasElement.height, 4, 0, 2 * Math.PI);
ctx.fill();
});
}
function drawConnections(landmarks) {
const connections = [
[0, 1], [1, 2], [2, 3], [3, 7], [0, 4], [4, 5], [5, 6], [6, 8],
[11, 13], [13, 15], [15, 17], [15, 19], [15, 21],
[12, 14], [14, 16], [16, 18], [16, 20], [16, 22],
[11, 12], [11, 23], [12, 24], [23, 24],
[23, 25], [25, 27], [27, 29], [27, 31],
[24, 26], [26, 28], [28, 30], [28, 32]
];
ctx.strokeStyle = '#00FF00';
ctx.lineWidth = 2;
connections.forEach(([i, j]) => {
if (landmarks[i] && landmarks[j]) {
ctx.beginPath();
ctx.moveTo(landmarks[i].x * canvasElement.width, landmarks[i].y * canvasElement.height);
ctx.lineTo(landmarks[j].x * canvasElement.width, landmarks[j].y * canvasElement.height);
ctx.stroke();
}
});
}
async function predictPose(landmarks) {
if (!model) return;
const inputTensor = tf.tensor2d([landmarks]);
const prediction = model.predict(inputTensor);
const [predictedClass] = await prediction.argMax(1).data();
const confidence = await prediction.max(1).data();
poseLabelElement.textContent = poseClasses[predictedClass] || 'Unknown';
poseAccuracyElement.textContent = `${(confidence[0]).toFixed(4)}`;
inputTensor.dispose();
prediction.dispose();
}
function startCamera() {
camera = new Camera(videoElement, {
onFrame: async () => {
await poseDetector.send({ image: videoElement });
},
width: 640,
height: 480
});
camera.start();
}
async function init() {
await loadModel();
setupPose();
startCamera();
}
document.addEventListener('DOMContentLoaded', init);
</script>
<style>
.section-title {
text-align: center;
font-size: 1.8rem;
margin-bottom: 20px;
color: #001f3f; /* biru dongker */
}
.about__container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
}
.video-container {
position: relative;
width: 640px;
height: 480px;
border: 2px solid #ccc;
border-radius: 10px;
overflow: hidden;
}
.prediction-box {
flex-shrink: 0;
width: 280px;
height: fit-content;
padding: 20px;
background: #f4f4f4;
text-align: center;
border-radius: 10px;
font-size: 1.2rem;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
#pose-label {
font-weight: bold;
color: #2c3e50;
}
#pose-accuracy {
font-weight: bold;
color: #27ae60;
}
</style>
@endsection

View File

@ -0,0 +1,289 @@
@extends('layouts.app')
@section('title', 'Deteksi Video')
@section('contents')
<section class="about section" id="about">
<h2 class="section-title text-center">Deteksi Pose Jurus 1 Tangan Kosong IPSI</h2>
<div class="about__container">
<div style="display: flex; gap: 20px; flex-wrap: wrap; justify-content: center;">
<div class="video-container">
<video id="video-upload" controls></video>
<canvas id="output-canvas"></canvas>
</div>
<div>
<div class="text-center mt-4 flex gap-3 justify-center">
<label class="cursor-pointer inline-block px-6 py-3 bg-blue-600 text-white rounded-xl shadow hover:bg-blue-700 transition">
Upload Video
<input type="file" accept="video/*" onchange="handleVideoUpload(event)" class="hidden" id="video-input">
</label>
<button onclick="resetVideo()" class="inline-block px-6 py-3 bg-red-600 text-white rounded-xl shadow hover:bg-red-700 transition">
Reset
</button>
</div>
<div id="prediction-result" class="prediction-box mt-4">
<p>Prediksi: <span id="pose-label">-</span></p>
<p>Probability: <span id="pose-accuracy">-</span></p>
<ul id="all-probabilities" class="text-left mt-2"></ul>
</div>
</div>
</div>
</div>
</section>
@endsection
@section('script')
<script src="https://code.jquery.com/jquery-3.6.0.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/pose"></script>
<script>
const poseClasses = [
'A1_benar', 'A1_salah','A2_benar', 'A2_salah', 'A3_benar', 'A3_salah',
'A4_benar', 'A4_salah', 'A5_benar', 'A5_salah', 'A6_benar', 'A6_salah',
'A7_benar', 'A7_salah'
];
const videoElement = document.getElementById('video-upload');
const canvasElement = document.getElementById('output-canvas');
const ctx = canvasElement.getContext('2d');
const poseLabelElement = document.getElementById('pose-label');
const poseAccuracyElement = document.getElementById('pose-accuracy');
const fileInput = document.getElementById('video-input');
const probList = document.getElementById('all-probabilities');
let model = null;
let poseDetector = null;
let animationFrameId = null;
async function loadTFModel() {
try {
model = await tf.loadLayersModel('/models/model.json');
console.log('✅ Model loaded');
} catch (error) {
alert('Model gagal dimuat');
}
}
function setupPoseDetection() {
poseDetector = new Pose({
locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/pose/${file}`
});
poseDetector.setOptions({
modelComplexity: 1,
smoothLandmarks: true,
enableSegmentation: false,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5
});
poseDetector.onResults(onPoseResults);
}
function onPoseResults(results) {
if (!results.poseLandmarks) return;
ctx.clearRect(0, 0, canvasElement.width, canvasElement.height);
ctx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
drawLandmarks(results.poseLandmarks);
const landmarks = results.poseLandmarks.map(landmark => [
landmark.x * canvasElement.height,
landmark.y * canvasElement.height,
landmark.z * canvasElement.width
]).flat();
predictPose(landmarks);
}
function drawLandmarks(landmarks) {
drawConnections(landmarks);
ctx.fillStyle = '#FF0000';
landmarks.forEach(landmark => {
ctx.beginPath();
ctx.arc(landmark.x * canvasElement.width, landmark.y * canvasElement.height, 5, 0, 2 * Math.PI);
ctx.fill();
});
}
function drawConnections(landmarks) {
const connections = [
[0, 1], [1, 2], [2, 3], [3, 7], [0, 4], [4, 5], [5, 6], [6, 8],
[11, 13], [13, 15], [15, 17], [15, 19], [15, 21],
[12, 14], [14, 16], [16, 18], [16, 20], [16, 22],
[11, 12], [11, 23], [12, 24], [23, 24],
[23, 25], [25, 27], [27, 29], [27, 31],
[24, 26], [26, 28], [28, 30], [28, 32]
];
ctx.strokeStyle = '#00FF00';
ctx.lineWidth = 2;
connections.forEach(([i, j]) => {
if (landmarks[i] && landmarks[j]) {
ctx.beginPath();
ctx.moveTo(landmarks[i].x * canvasElement.width, landmarks[i].y * canvasElement.height);
ctx.lineTo(landmarks[j].x * canvasElement.width, landmarks[j].y * canvasElement.height);
ctx.stroke();
}
});
}
async function predictPose(landmarks) {
if (!model) return;
const inputTensor = tf.tensor2d([landmarks]);
const prediction = model.predict(inputTensor);
const predictionData = await prediction.data();
const [predictedClass] = await prediction.argMax(1).data();
const confidence = predictionData[predictedClass];
poseLabelElement.textContent = poseClasses[predictedClass] || 'Unknown';
poseAccuracyElement.textContent = `${confidence.toFixed(4)}`;
// Tampilkan semua probabilitas
probList.innerHTML = '';
predictionData.forEach((prob, idx) => {
const li = document.createElement('li');
li.textContent = `${poseClasses[idx] || 'Label-' + idx}: ${prob.toFixed(4)}`;
probList.appendChild(li);
});
inputTensor.dispose();
prediction.dispose();
}
function handleVideoUpload(event) {
const file = event.target.files[0];
if (!file) return;
const url = URL.createObjectURL(file);
videoElement.src = url;
videoElement.load();
videoElement.onloadedmetadata = () => {
canvasElement.width = videoElement.videoWidth;
canvasElement.height = videoElement.videoHeight;
};
}
function analyzeVideoFrame() {
if (!poseDetector || !videoElement) return;
const process = async () => {
if (videoElement.paused || videoElement.ended) {
cancelAnimationFrame(animationFrameId);
return;
}
await poseDetector.send({ image: videoElement });
animationFrameId = requestAnimationFrame(process);
};
animationFrameId = requestAnimationFrame(process);
}
function resetVideo() {
videoElement.pause();
videoElement.currentTime = 0;
videoElement.removeAttribute('src');
videoElement.load();
ctx.clearRect(0, 0, canvasElement.width, canvasElement.height);
poseLabelElement.textContent = '-';
poseAccuracyElement.textContent = '-';
probList.innerHTML = '';
cancelAnimationFrame(animationFrameId);
fileInput.value = '';
}
videoElement.addEventListener('play', () => {
analyzeVideoFrame();
});
videoElement.addEventListener('pause', () => {
cancelAnimationFrame(animationFrameId);
});
videoElement.addEventListener('ended', () => {
cancelAnimationFrame(animationFrameId);
});
async function initializeApp() {
await loadTFModel();
setupPoseDetection();
}
document.addEventListener('DOMContentLoaded', initializeApp);
</script>
<style>
.section-title {
text-align: center;
margin-bottom: 10px;
font-weight: bold;
font-size: 1.8rem;
}
.video-container {
position: relative;
width: 640px;
height: 480px;
border: 2px solid #ddd;
border-radius: 8px;
overflow: hidden;
background-color: black;
}
video, canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
}
.prediction-box {
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
text-align: center;
font-size: 1.2rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
min-width: 300px;
}
#pose-label {
font-weight: bold;
color: #2c3e50;
}
#pose-accuracy {
font-weight: bold;
color: #27ae60;
}
#all-probabilities {
max-height: 200px;
overflow-y: auto;
padding-left: 0;
list-style: none;
margin-top: 10px;
font-size: 0.95rem;
text-align: left;
}
#all-probabilities li {
margin-bottom: 4px;
}
canvas {
pointer-events: none;
}
</style>
@endsection

View File

@ -0,0 +1,43 @@
@extends('layouts.app')
@section('title', 'Home')
@section('contents')
<section class="home bd-grid" id="home">
<div class="home__data">
<h1 class="home__title">Welcome,<br>Pose<span class="home__title-color"> Detection</span><br> Website</h1>
<a href="{{ route('realtime') }}" class="button">Coba Deteksi</a>
</div>
<div class="home__social">
</div>
<div class="home__img">
<svg class="home__blob" viewBox="0 0 479 467" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<mask id="mask0" mask-type="alpha">
<path d="M9.19024 145.964C34.0253 76.5814 114.865 54.7299 184.111 29.4823C245.804 6.98884 311.86 -14.9503 370.735 14.143C431.207 44.026 467.948 107.508 477.191 174.311C485.897 237.229 454.931 294.377 416.506 344.954C373.74 401.245 326.068 462.801 255.442 466.189C179.416 469.835 111.552 422.137 65.1576 361.805C17.4835 299.81 -17.1617 219.583 9.19024 145.964Z"/>
</mask>
<g mask="url(#mask0)">
<path d="M9.19024 145.964C34.0253 76.5814 114.865 54.7299 184.111 29.4823C245.804 6.98884 311.86 -14.9503 370.735 14.143C431.207 44.026 467.948 107.508 477.191 174.311C485.897 237.229 454.931 294.377 416.506 344.954C373.74 401.245 326.068 462.801 255.442 466.189C179.416 469.835 111.552 422.137 65.1576 361.805C17.4835 299.81 -17.1617 219.583 9.19024 145.964Z"/>
<image class="home__blob-img" x="90" y="30" href="{{asset('assets/img/silat.png')}}"/>
</g>
</svg>
</div>
</section>
<!--===== ABOUT =====-->
<section class="about section " id="about">
<h2 class="section-title">About</h2>
<div class="about__container bd-grid">
<div class="about__img">
<img src="{{asset('assets/img/silat.png')}}" alt="">
</div>
<div>
<h2 class="about__subtitle">PoseAI</h2>
<p class="about__text">Merupakan website untuk deteksi 7 gerakan pada jurus 1 tangan kosong IPSI. Menggunakan teknologi AI yaitu Mediapipe untuk model deteksinya.</p>
</div>
</div>
</section>
@endsection

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="{{ asset('assets/img/silat2.png') }}">
<link rel="stylesheet" href="{{asset('assets/css/styles.css')}}">
<!-- =====BOX ICONS===== -->
<link href='https://cdn.jsdelivr.net/npm/boxicons@2.0.5/css/boxicons.min.css' rel='stylesheet'>
<title>Deteksi Pose </title>
</head>
<body>
<!--===== HEADER =====-->
<header class="l-header">
@include('layouts.navbar')
</header>
<main class="l-main">
<!--===== HOME =====-->
@yield('contents')
@yield('script')
</main>
<!--===== FOOTER =====-->
@include('layouts.footer')
<!--===== SCROLL REVEAL =====-->
<script src="https://unpkg.com/scrollreveal"></script>
<!--===== MAIN JS =====-->
<script src="{{asset('assets/js/main.js')}}"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
<footer class="footer">
<p class="footer__title">Contact</p>
<div class="footer__social">
<a href="https://id.linkedin.com/in/eka-sulistyaningsih" class="footer__icon"><i class='bx bxl-linkedin' ></i></a>
<a href="https://github.com/ekastn15" class="footer__icon"><i class='bx bxl-github' ></i></a>
<a href="https://wa.me/6282127686455" class="footer__icon"><i class='bx bxl-whatsapp' ></i></a>
</div>
<p class="footer__copy">&#169; 2025. All rigths reserved</p>
</footer>

View File

@ -0,0 +1,23 @@
<nav class="nav bd-grid">
<div class="nav__logo">
<img src="{{ asset('assets/img/silat.png') }}" alt="Logo" class="logo-img">
<span>PoseAI Silat</span>
</div>
<div class="nav__menu" id="nav-menu">
<ul class="nav__list">
<li class="nav__item"><a href="{{ route('home') }}" class="nav__link {{ Request::is('/') ? 'active-link' : '' }}">Beranda</a></li>
<li class="nav__item"><a href="{{ route('tutorial') }}" class="nav__link nav__link {{ Request::is('tutorial') ? 'active-link' : '' }}">Tutorial</a></li>
<li class="nav__item nav__dropdown">Deteksi
<ul class="dropdown__menu">
<li><a href="{{ route('upload') }}" class="dropdown__link">Upload Video</a></li>
<li><a href="{{ route('realtime') }}" class="dropdown__link">Realtime</a></li>
</ul>
</li>
</ul>
</div>
<div class="nav__toggle" id="nav-toggle">
<i class='bx bx-menu'></i>
</div>
</nav>

View File

@ -0,0 +1,48 @@
@extends('layouts.app')
@section('title', 'Tutorial')
@section('contents')
<!--===== WORK (TUTORIAL VIDEO) =====-->
<section class="work section" id="work">
<h2 class="section-title">Tutorial</h2>
<div class="work__container bd-grid">
<video class="tutor__video" controls>
<source src="{{ asset('assets/videos/A1003.mp4') }}" type="video/mp4">
Browser Anda tidak mendukung tag video.
</video>
<video class="tutor__video" controls>
<source src="{{ asset('assets/videos/A2003.mp4') }}" type="video/mp4">
Browser Anda tidak mendukung tag video.
</video>
<video class="tutor__video" controls>
<source src="{{ asset('assets/videos/A3003.mp4') }}" type="video/mp4">
Browser Anda tidak mendukung tag video.
</video>
<video class="tutor__video" controls>
<source src="{{ asset('assets/videos/A4003.mp4') }}" type="video/mp4">
Browser Anda tidak mendukung tag video.
</video>
<video class="tutor__video" controls>
<source src="{{ asset('assets/videos/A5003.mp4') }}" type="video/mp4">
Browser Anda tidak mendukung tag video.
</video>
<video class="tutor__video" controls>
<source src="{{ asset('assets/videos/A6003.mp4') }}" type="video/mp4">
Browser Anda tidak mendukung tag video.
</video>
<video class="tutor__video" controls>
<source src="{{ asset('assets/videos/A7003.mp4') }}" type="video/mp4">
Browser Anda tidak mendukung tag video.
</video>
</div>
</section>
@endsection

File diff suppressed because one or more lines are too long

19
routes/api.php Normal file
View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});

18
routes/channels.php Normal file
View File

@ -0,0 +1,18 @@
<?php
use Illuminate\Support\Facades\Broadcast;
/*
|--------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});

19
routes/console.php Normal file
View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
/*
|--------------------------------------------------------------------------
| Console Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of your Closure based console
| commands. Each Closure is bound to a command instance allowing a
| simple approach to interacting with each command's IO methods.
|
*/
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

34
routes/web.php Normal file
View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/
Route::get('/',function () {
return view('home');
})->name('home');
Route::get('/tutorial',function () {
return view('tutorial.index');
})->name('tutorial');
Route::get('/deteksi/upload',function () {
return view('deteksi.upload');
})->name('upload');
Route::get('/deteksi/realtime',function () {
return view('deteksi.realtime');
})->name('realtime');
// Route::get('/', function () {
// return view('welcome');
// });