Compare commits

...

2 Commits

15 changed files with 108 additions and 41 deletions

View File

@ -1,23 +1,38 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<link href="/avatar-bot.png" rel="icon" type="image/png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta content="Portfolio de Pablo de la Torre" name="description"/>
<meta name="keywords" content="desarrollador, full stack, vue.js, react, node.js, portfolio, programador" />
<meta content="Pablo de la Torre" name="author"/>
<link href="/manifest.json" rel="manifest"/>
<meta content="#3b1070" name="theme-color"/>
<meta content="#3b1070" name="background-color"/>
<meta content="#3b1070" name="apple-mobile-web-app-status-bar-style">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta content="https://pablotj.com" property="og:url"/>
<meta content="Portfolio | Pablot TJ" property="og:title"/>
<meta content="Portfolio de Pablo de la Torre" property="og:description"/>
<title>Portfolio | Pablot TJ</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Portfolio | Pablo de la Torre Jamardo (Pablo TJ)</title>
<!-- SEO -->
<meta name="description" content="Portfolio personal de Pablo de la Torre Jamardo (Pablo TJ): proyectos, desarrollo software, programación y soluciones tecnológicas." />
<meta name="keywords" content="portfolio, Pablo TJ, Pablo de la Torre, desarrollador, programación, software, tecnología" />
<meta name="robots" content="index, follow" />
<meta name="author" content="Pablo de la Torre Jamardo" />
<link rel="canonical" href="https://pablotj.com/" />
<!-- PWA / Mobile -->
<meta name="theme-color" content="#062342" />
<meta name="apple-mobile-web-app-title" content="Pablo TJ" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<!-- Favicon & App Icons -->
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png" />
<link rel="manifest" href="/images/site.webmanifest" />
<link rel="shortcut icon" href="/images/favicon.ico" />
<!-- Open Graph (Facebook, LinkedIn, WhatsApp) -->
<meta property="og:title" content="Portfolio | Pablo de la Torre Jamardo (Pablo TJ)" />
<meta property="og:description" content="Explora el portfolio de Pablo TJ: proyectos de software, programación y desarrollo tecnológico." />
<meta property="og:image" content="https://pablotj.com/images/favicon.svg" />
<meta property="og:url" content="https://pablotj.com/" />
<meta property="og:type" content="website" />
</head>
<body>
<div id="app"></div>

10
package-lock.json generated
View File

@ -12,6 +12,7 @@
"vue": "^3.4.21",
"vue-loading-overlay": "^6.0.6",
"vue-preloader": "^1.1.4",
"vue-toastification": "^2.0.0-rc.5",
"vue-typer": "^1.2.0",
"vue3-typer": "^1.0.0"
},
@ -2963,6 +2964,15 @@
"node": ">=14"
}
},
"node_modules/vue-toastification": {
"version": "2.0.0-rc.5",
"resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz",
"integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.2"
}
},
"node_modules/vue-typer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/vue-typer/-/vue-typer-1.2.0.tgz",

View File

@ -30,6 +30,7 @@
"vue": "^3.4.21",
"vue-loading-overlay": "^6.0.6",
"vue-preloader": "^1.1.4",
"vue-toastification": "^2.0.0-rc.5",
"vue-typer": "^1.2.0",
"vue3-typer": "^1.0.0"
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 496 KiB

View File

@ -0,0 +1,21 @@
{
"name": "Pablo TJ",
"short_name": "Pablo TJ",
"icons": [
{
"src": "/favicon.ico/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/favicon.ico/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

View File

@ -3,17 +3,17 @@
"name": "Pablot TJ",
"icons": [
{
"src": "avatar-bot.png",
"src": "images/favicon-96x96.png",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "avatar-bot.png",
"src": "images/web-app-manifest-192x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "avatar-bot.png",
"src": "images/web-app-manifest-512x512.png",
"type": "image/png",
"sizes": "512x512"
}

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -2,7 +2,8 @@
import {ref} from 'vue'
import {Github, Globe, Linkedin, Mail, MapPin, Phone} from 'lucide-vue-next'
import type {Profile} from '@/domain/models/Profile'
import { useToast } from "vue-toastification";
const toast = useToast();
defineProps<{
profile: Profile
}>()
@ -27,23 +28,37 @@ function getSocialIcon(platform) {
async function handleSubmit() {
isSubmitting.value = true
// Simulate form submission
await new Promise(resolve => setTimeout(resolve, 1000))
try {
const payload = {
from: form.value.email,
subject: form.value.name,
body: form.value.message
}
// Here you would typically send the form data to your backend
console.log('Form submitted:', form.value)
const response = await fetch(`${import.meta.env.VITE_MAIL_API_URL}/mail`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
// Reset form
form.value = {
name: '',
email: '',
message: ''
if (!response.ok) {
throw new Error(`Error en el envío: ${response.status}`)
}
form.value = {
name: '',
email: '',
message: ''
}
toast.success("✅ ¡Mensaje enviado correctamente! Te responderé pronto.")
} catch (error) {
toast.error("❌ Hubo un error al enviar el mensaje. Inténtalo de nuevo.")
} finally {
isSubmitting.value = false
}
isSubmitting.value = false
// Show success message (you could use a toast notification)
alert('¡Mensaje enviado correctamente! Te responderé pronto.')
}
</script>
@ -117,15 +132,12 @@ async function handleSubmit() {
<!-- Contact Form -->
<div class="bg-gray-50 dark:bg-gray-700 rounded-xl p-8">
<p class="m-5">¡Hola! Por el momento mi servidor SMTP está de vacaciones 😅.</p>
<p class="m-5">Si quieres contactarme, envíame un correo electrónico directamente y prometo responderte
rápido.</p>
<form @submit.prevent="handleSubmit" class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Nombre
</label>
<input disabled
<input
v-model="form.name"
type="text"
required
@ -137,7 +149,7 @@ async function handleSubmit() {
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Email
</label>
<input disabled
<input
v-model="form.email"
type="email"
required
@ -149,7 +161,7 @@ async function handleSubmit() {
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Mensaje
</label>
<textarea disabled
<textarea
v-model="form.message"
rows="4"
required
@ -159,7 +171,7 @@ async function handleSubmit() {
<button
type="submit"
:disabled="isSubmitting || 1===1"
:disabled="isSubmitting"
class="w-full px-6 py-3 bg-purple-600 hover:bg-purple-700 disabled:opacity-50 text-white rounded-lg font-semibold transition-colors"
>
{{ isSubmitting ? 'Enviando...' : 'Enviar Mensaje' }}

View File

@ -3,10 +3,14 @@ import "./style.css"
import VueTyper from 'vue3-typer'
import "vue3-typer/dist/vue-typer.css"
import App from "./App.vue";
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";
const app = createApp(App)
app.component('VueTyper', VueTyper)
app.use(Toast, {});
// Global error handler
app.config.errorHandler = (err, vm, info) => {
console.error("Global error:", err, info)