Initial commit
This commit is contained in:
commit
3ea52e4b54
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 266 KiB |
Binary file not shown.
After Width: | Height: | Size: 137 KiB |
|
@ -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});
|
|
@ -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,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);
|
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -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.
|
@ -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"}]}]}
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
|
@ -0,0 +1 @@
|
||||||
|
import './bootstrap';
|
|
@ -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'],
|
||||||
|
// });
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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">© 2025. All rigths reserved</p>
|
||||||
|
</footer>
|
|
@ -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>
|
|
@ -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
|
@ -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();
|
||||||
|
});
|
|
@ -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;
|
||||||
|
});
|
|
@ -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');
|
|
@ -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');
|
||||||
|
// });
|
Loading…
Reference in New Issue