This commit is contained in:
2026-06-08 20:48:23 +02:00
parent b224c2ba65
commit 89277dc1fb
22 changed files with 155 additions and 287 deletions

View File

@@ -19,7 +19,7 @@ body {
* {
color: var(--color-text);
font-family: 'Hurmit';
cursor: text;
cursor: default;
}
.pixelated {

View File

@@ -1,71 +0,0 @@
<script lang="js" setup>
import HeaderLinks from './HeaderLinks.vue';
import SiteOptions from './site_options/SiteOptions.vue';
</script>
<template>
<div class="tui-minimalbar">
<div class="minimal-inner">
<div class="left">
<span class="sticky-title" aria-hidden="true"> ARANROIG.COM</span>
<HeaderLinks />
</div>
<SiteOptions />
</div>
</div>
<div style="height: 80px"></div>
</template>
<style lang="scss" scoped>
.tui-minimalbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background: var(--color-sticky-header-bg);
backdrop-filter: blur(8px);
box-shadow: 0 4px 0px 0px var(--color-container-shadow);
}
.minimal-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 6px 24px;
@media screen and (max-width: 900px) {
padding: 5px 16px;
}
@media screen and (max-width: 600px) {
padding: 4px 12px;
gap: 0.5rem;
}
}
.left {
display: flex;
align-items: center;
gap: 1rem;
@media screen and (max-width: 600px) {
gap: 0.5rem;
}
}
.sticky-title {
font-family: 'Hurmit', monospace;
font-size: 0.8rem;
color: var(--color-text);
letter-spacing: 1px;
white-space: nowrap;
@media screen and (max-width: 900px) {
font-size: 0.75rem;
}
}
</style>

View File

@@ -1,107 +0,0 @@
<script setup lang="ts">
import HeaderLinks from './HeaderLinks.vue';
import SiteOptions from './site_options/SiteOptions.vue';
import StickyHeader from './StickyHeader.vue';
const asciiLines = [
"░█▀█░█▀▄░█▀█░█▀█░█▀▄░█▀█░▀█▀░█▀▀",
"░█▀█░█▀▄░█▀█░█░█░█▀▄░█░█░░█░░█░█",
"░▀░▀░▀░▀░▀░▀░▀░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀"
];
const revealedLines = ref<number>(0);
let asciiTimer: ReturnType<typeof setInterval> | null = null;
const HAS_ANIMATED_KEY = 'ascii-animated';
function startAsciiAnimation() {
if (sessionStorage.getItem(HAS_ANIMATED_KEY)) {
revealedLines.value = asciiLines.length;
return;
}
const delay = 400;
let count = 0;
asciiTimer = setInterval(() => {
count++;
revealedLines.value = count;
if (count >= asciiLines.length) {
clearInterval(asciiTimer!);
asciiTimer = null;
sessionStorage.setItem(HAS_ANIMATED_KEY, '1');
}
}, delay);
}
onMounted(() => {
startAsciiAnimation();
});
onBeforeUnmount(() => {
if (asciiTimer) clearInterval(asciiTimer);
});
</script>
<template>
<div class="header">
<div class="container">
<div class="header-container website">
<pre v-if="revealedLines > 0" class="ascii-title" aria-hidden="true">{{ asciiLines.slice(0, revealedLines).join('\n') }}</pre>
<HeaderLinks></HeaderLinks>
</div>
</div>
<div class="header-container">
</div>
<div class="container">
<SiteOptions></SiteOptions>
</div>
</div>
<StickyHeader></StickyHeader>
</template>
<style lang="scss" scoped>
.header {
position: relative;
margin-top: 30px;
user-select: none;
display: flex;
justify-content: space-between;
}
.header-container {
&.website {
display: flex;
flex-direction: column;
gap: 8px;
@media screen and (max-width: 600px) {
margin-top: 20px;
}
}
}
.header-container {
position:relative;
margin-bottom: 20px;
}
.ascii-title {
font-family: 'Hurmit', monospace;
color: var(--color-link);
text-shadow: 0 0 8px var(--color-link), 0 0 4px var(--color-link);
font-size: clamp(0.35rem, 1.2vw, 0.65rem);
line-height: 1;
letter-spacing: -0.1ch;
margin: 0;
white-space: pre;
min-height: clamp(1.05rem, 3.6vw, 1.95rem);
}
@media screen and (max-width: 600px) {
.ascii-title {
font-size: clamp(0.28rem, 1.8vw, 0.5rem);
line-height: 1;
letter-spacing: -0.1ch;
}
}
</style>

View File

@@ -1,6 +1,5 @@
<script setup>
import MinimalHeader from '~/components/parts/MinimalHeader.vue';
import PageHeader from '~/components/parts/PageHeader.vue';
import TableHeader from '~/components/parts/TableHeader.vue';
const slug = useRoute().params.slug;
const { locale } = useI18n();
@@ -12,7 +11,9 @@ const { data: post } = await useAsyncData(`art-${slug}`, () =>
<template>
<!-- Render the blog post as Prose & Vue components -->
<MinimalHeader></MinimalHeader>
<div class="no-sprite">
<TableHeader></TableHeader>
</div>
<div class="extended-container">
<ContentRenderer v-if="post" :value="post" class="art" />
</div>
@@ -39,4 +40,10 @@ const { data: post } = await useAsyncData(`art-${slug}`, () =>
max-width: 100%;
}
}
</style>
<style lang="scss">
.no-sprite .undertable-wrapper {
display: none;
}
</style>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import FixedLayout from '~/components/layouts/FixedLayout.vue';
import MinimalHeader from '~/components/parts/MinimalHeader.vue';
import TableHeader from '~/components/parts/TableHeader.vue';
const { locale, t } = useI18n();
const localePath = useLocalePath();
@@ -53,7 +53,9 @@ const displayedArt = computed(() => {
</script>
<template>
<MinimalHeader></MinimalHeader>
<div class="no-sprite">
<TableHeader></TableHeader>
</div>
<FixedLayout>
<Container>
<h2 class="section-title">ART GALLERY</h2>
@@ -235,3 +237,9 @@ const displayedArt = computed(() => {
}
}
</style>
<style lang="scss">
.no-sprite .undertable-wrapper {
display: none;
}
</style>

View File

@@ -1,6 +1,6 @@
<script setup>
import FixedLayout from '~/components/layouts/FixedLayout.vue';
import PageHeader from '~/components/parts/PageHeader.vue';
import TableHeader from '~/components/parts/TableHeader.vue';
const slug = useRoute().params.slug;
const { locale } = useI18n();
@@ -12,7 +12,9 @@ const { data: post } = await useAsyncData(`blog-${slug}`, () =>
<template>
<!-- Render the blog post as Prose & Vue components -->
<PageHeader></PageHeader>
<div class="no-sprite">
<TableHeader></TableHeader>
</div>
<FixedLayout>
<Container>
<ContentRenderer v-if="post" :value="post" class="blog" />
@@ -36,4 +38,10 @@ const { data: post } = await useAsyncData(`blog-${slug}`, () =>
max-height: 400px;
}
}
</style>
<style lang="scss">
.no-sprite .undertable-wrapper {
display: none;
}
</style>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import MinimalHeader from '~/components/parts/MinimalHeader.vue';
import { useAsyncData } from '#app';
import TableHeader from '~/components/parts/TableHeader.vue';
import FixedLayout from '~/components/layouts/FixedLayout.vue';
import { ref, computed } from 'vue';
const { locale } = useI18n();
@@ -26,7 +25,9 @@ const displayedPosts = computed(() => {
</script>
<template>
<MinimalHeader></MinimalHeader>
<div class="no-sprite">
<TableHeader></TableHeader>
</div>
<FixedLayout>
<Container>
<section class="blog-section">
@@ -165,3 +166,9 @@ const displayedPosts = computed(() => {
}
}
</style>
<style lang="scss">
.no-sprite .undertable-wrapper {
display: none;
}
</style>

View File

@@ -13,7 +13,9 @@ const { data: markdown } = await useAsyncData(`fixed`, async () =>
</script>
<template>
<TableHeader></TableHeader>
<div class="no-sprite">
<TableHeader></TableHeader>
</div>
<FixedLayout>
<Container>
@@ -36,4 +38,10 @@ p {
display: flex;
width: 100%;
}
</style>
<style lang="scss">
.no-sprite .undertable-wrapper {
display: none;
}
</style>

View File

@@ -29,6 +29,19 @@ const { data: contactMarkdown } = await useAsyncData(`fixed-contact`, async () =
const localePath = useLocalePath();
function resolveProjectLink(project) {
if (project.link) return project.link;
const slugToProjectMap = {
'dragonroll': 'https://dragonroll.aranroig.com',
};
return slugToProjectMap[project.slug] || null;
}
function navigateToProject(project) {
const link = resolveProjectLink(project);
if (link) window.open(link, '_blank', 'noopener,noreferrer');
}
const artPosts = useState<any[]>('art-posts', () => null as any);
if (!artPosts.value?.length) {
const currentLocale = locale.value;
@@ -125,33 +138,37 @@ const sectionTargets = {
<section class="projects-section" id="scroll-projects" v-if="projects && projects.length > 0">
<Container>
<h2 class="section-title">{{ t('pages.projects_heading') }}</h2>
<!--
<div class="projects-grid">
<div
v-for="project in projects"
v-for="project in projects"
:key="project.slug"
class="project-card"
class="project-card-wrapper"
>
<span class="project-card-corner tl"></span>
<span class="project-card-corner tr"></span>
<span class="project-card-corner bl"></span>
<span class="project-card-corner br"></span>
<a
:href="project.link"
target="_blank"
rel="noopener noreferrer"
class="project-title"
>{{ project.title }}</a>
<p class="project-description">{{ project.description }}</p>
<div class="project-tech">
<span v-for="tech in project.tech" :key="tech" class="tech-tag">{{ tech }}</span>
<div
class="project-card"
@click="navigateToProject(project)"
>
<div class="project-layout">
<img
v-if="project.preview"
:src="project.preview"
:alt="`Preview of ${project.title}`"
class="project-preview"
/>
<div class="project-info">
<p class="project-title">{{ project.title }}</p>
<p class="project-description">{{ project.description }}</p>
<div class="project-tech">
<span v-for="tech in project.tech" :key="tech" class="tech-tag">{{ tech }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
-->
(Under construction...)
</Container>
</section>
@@ -388,11 +405,6 @@ const sectionTargets = {
padding: 12px 16px;
transition: all 0.1s steps(2, end);
&:hover {
border-color: var(--color-link);
box-shadow: 4px -4px 0px 0px var(--color-link);
}
&::before {
content: "[ PROJECT ]";
position: absolute;
@@ -404,6 +416,28 @@ const sectionTargets = {
color: var(--color-link);
letter-spacing: 2px;
}
.project-layout {
display: flex;
gap: 16px;
}
.project-preview {
width: 140px;
min-width: 140px;
height: 85px;
object-fit: cover;
border: 1px solid var(--color-border-color);
image-rendering: auto;
transition: border-color 0.1s steps(2, end);
}
.project-info {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
}
.project-card-corner {
@@ -420,18 +454,29 @@ const sectionTargets = {
.project-title {
font-family: 'Hurmit', monospace;
color: var(--color-link);
color: inherit;
text-decoration: none;
font-size: 1.05rem;
text-shadow: 0 0 4px var(--color-link);
display: block;
text-shadow: inherit;
&:hover {
color: var(--color-link);
text-shadow: 0 0 12px var(--color-link), 0 0 4px var(--color-link);
}
}
.project-card {
cursor: pointer;
}
.project-card-wrapper:hover .project-card {
border-color: var(--color-link);
box-shadow: 4px -4px 0px 0px var(--color-link);
}
.project-info {
cursor: pointer;
}
.project-description {
font-family: 'Hurmit', monospace;
color: var(--color-text);
@@ -762,6 +807,16 @@ const sectionTargets = {
.project-card {
padding: 8px 12px;
}
.project-layout {
flex-direction: column !important;
}
.project-preview {
width: 100% !important;
min-width: auto !important;
height: 160px !important;
}
.project-title {
font-size: 0.95rem;

View File

@@ -38,6 +38,7 @@ export default defineContentConfig({
slug: z.string(),
description: z.string(),
link: z.string().optional(),
preview: z.string().optional(),
tech: z.array(z.string()).default([])
})
}),

View File

@@ -1,8 +0,0 @@
---
title: App de Codelearn
slug: codelearn-app
date: 2026-01-01
description: Una aplicació full-stack de gestió d'aprenentatge construïda al meu temps a Codelearn. Autenticació d'usuaris, gestió de cursos i seguiment del progrés en temps real.
link: https://codelearn.cat
tech: ["Nuxt", "Express", "MongoDB", "TypeScript"]
---

View File

@@ -0,0 +1,7 @@
---
title: Dragonroll
slug: dragonroll
date: 2026-01-01
description: Un assistent de codi obert per a jocs de rol. Fes un seguiment de personatges, comparteix notes, reprodueix música, planifica encontres i gestiona ítems i encants.
tech: ["Nuxt", "Vue", "Express", "MongoDB"]
---

View File

@@ -1,7 +0,0 @@
---
title: Eina Pipeline ML
slug: ml-pipeline
date: 2026-01-01
description: Una eina modular de pipeline d'aprenentatge automàtic per al màster d'Aprenentatge Automàtic i Ciberseguretat de la UPC. Automatització del preprocesament, entrenament i avaluació de models.
tech: ["Python", "PyTorch", "Docker"]
---

View File

@@ -1,8 +0,0 @@
---
title: Portfolio Website
slug: portfolio-website
date: 2026-01-01
description: Aquesta mateixa pàgina web, construïda amb Nuxt 4, Vue 3 i SCSS. Estètica pixel-art, temes fosc/clar, personalització de colors d'accent i suport multilingüe en anglès, castellà i català.
link: https://aranroig.com
tech: ["Nuxt 4", "Vue 3", "SCSS", "Node.js", "MongoDB"]
---

View File

@@ -1,8 +0,0 @@
---
title: Codelearn Full-Stack App
slug: codelearn-app
date: 2026-01-01
description: A full-stack learning management application built during my time at Codelearn. Features user authentication, course management, and real-time progress tracking.
link: https://codelearn.cat
tech: ["Nuxt", "Express", "MongoDB", "TypeScript"]
---

View File

@@ -0,0 +1,7 @@
---
title: Dragonroll
slug: dragonroll
date: 2026-01-01
description: An open-source helper for role-playing games. Track characters, share notes, play music, plan encounters, and manage items and spells.
tech: ["Nuxt", "Vue", "Express", "MongoDB"]
---

View File

@@ -1,7 +0,0 @@
---
title: ML Pipeline Tool
slug: ml-pipeline
date: 2026-01-01
description: A modular machine learning pipeline tool for UPC's Machine Learning and Cybersecurity master's degree. Automates data preprocessing, model training, and evaluation workflows.
tech: ["Python", "PyTorch", "Docker"]
---

View File

@@ -1,8 +0,0 @@
---
title: Portfolio Website
slug: portfolio-website
date: 2026-01-01
description: This very website, built with Nuxt 4, Vue 3, and SCSS. Features pixel-art aesthetics, dark/light themes, accent color customization, and multilingual support across English, Spanish, and Catalan.
link: https://aranroig.com
tech: ["Nuxt 4", "Vue 3", "SCSS", "Node.js", "MongoDB"]
---

View File

@@ -1,8 +0,0 @@
---
title: App de Codelearn
slug: codelearn-app
date: 2026-01-01
description: Una aplicación full-stack de gestión de aprendizaje construida en mi tiempo en Codelearn. Autenticación de usuarios, gestión de cursos y seguimiento de progreso en tiempo real.
link: https://codelearn.cat
tech: ["Nuxt", "Express", "MongoDB", "TypeScript"]
---

View File

@@ -0,0 +1,7 @@
---
title: Dragonroll
slug: dragonroll
date: 2026-01-01
description: Un asistente de código abierto para juegos de rol. Controla personajes, comparte notas, reproduce música, planea encuentros y gestiona objetos y hechizos.
tech: ["Nuxt", "Vue", "Express", "MongoDB"]
---

View File

@@ -1,7 +0,0 @@
---
title: Herramienta Pipeline ML
slug: ml-pipeline
date: 2026-01-01
description: Una herramienta modular de pipeline de aprendizaje automático para el máster de Aprendizaje Automático y Ciberseguridad de la UPC. Automatiza preprocesamiento, entrenamiento y evaluación de modelos.
tech: ["Python", "PyTorch", "Docker"]
---

View File

@@ -1,8 +0,0 @@
---
title: Portfolio Website
slug: portfolio-website
date: 2026-01-01
description: Esta página web, construida con Nuxt 4, Vue 3 y SCSS. Con estética pixel-art, temas oscuro/claro, personalización de colores de acento y soporte multilingue en inglés, español y catalán.
link: https://aranroig.com
tech: ["Nuxt 4", "Vue 3", "SCSS", "Node.js", "MongoDB"]
---