Skip to content

Commit df06d84

Browse files
committed
feat: el escaparate de proyectos con el sitio del cliente recorriéndose
La sección de proyectos de la Home deja de ser una cáscara con escena reservada. Con un solo trabajo entregable la pieza NO es una rejilla esperando compañía, sino un caso destacado a ancho completo, y el argumento son cifras verificables en vez de adjetivos: rendimiento, SEO y accesibilidad medidos con PageSpeed Insights, citando herramienta y fecha para que cualquiera pueda repetir la medición. - Nuevo ScrollShot: una captura de página completa vive dentro de un marco con barra de navegador y se traslada conforme bajas, así se recorre el sitio del cliente sin salir del nuestro. El teléfono se le superpone en una esquina y avanza a otra velocidad — sincronizados se leen mecánicos, desfasados dan profundidad. - Las capturas se sirven con <picture> en cadena de respaldo AVIF -> WebP -> PNG: el navegador se queda con el primer formato que entienda, 642 KB en AVIF frente a los 3.3 MB del PNG de reserva. - Dos salidas: el sitio en producción del cliente (la prueba) y la página que engloba todo el trabajo (la continuidad). - Sección `projects` en el esquema de i18n y en ambos idiomas, con el slug localizado de la página índice (/proyectos/ y /projects/). Incluye además el ajuste del favicon .ico con `sizes="any"`, que estaba pendiente en el árbol de trabajo. Técnico: el recorrido de las capturas se ata AL MARCO, no a la sección. Atarlo a la sección —mucho más alta y con el marco a media altura— hacía que al llegar a verlo el progreso fuera ya del 45 % y el visitante se perdiera la cabecera del sitio capturado. El recorrido se limita además en unidades de viewport, porque estas capturas son larguísimas (la móvil ronda 1:19) y recorrerlas enteras las hacía pasar volando; con el tope la velocidad queda por debajo de 1x respecto al scroll. No llegar al pie es aceptable; perderse la cabecera, no. El contraste sobre la banda de fuego se resuelve apartando el sol en móvil y con un velo bajo la tarjeta, no con sombras detrás del texto. Cabecera y caso comparten una sola rejilla en escritorio para que la tarjeta arranque a la altura del titular sin márgenes negativos, y desde 90rem se lleva algo más de ancho para que las cuatro cifras entren en una fila.
1 parent 583dce5 commit df06d84

17 files changed

Lines changed: 1035 additions & 9 deletions

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,21 @@ Todos los cambios notables en este proyecto serán documentados en este archivo
341341
### Técnico
342342

343343
- Los identificadores de página (`pageName`) siguen siendo estables e internos; la traducción a slug real vive en `pageSlug()` (`src/data/i18n.ts`), único punto donde se resuelve la equivalencia. El JSON-LD se omite por completo en las páginas fuera del índice y escapa `<` para que su contenido no pueda cerrar el `<script>` que lo aloja.
344+
345+
## [0.20.0] - 2026-07-19
346+
347+
### Añadido
348+
349+
- **Sección de proyectos en la Home — «el escaparate»** (`src/components/sections/Projects/`): con un solo trabajo entregable, la pieza NO es una rejilla esperando compañía, sino un **caso destacado a ancho completo**. El argumento son cifras verificables —rendimiento, SEO y accesibilidad medidos con PageSpeed Insights, citando herramienta y fecha— en vez de adjetivos. Dos salidas deliberadas: el sitio en producción del cliente (la prueba) y la página que engloba todo el trabajo (la continuidad).
350+
- **`src/components/ui/ScrollShot/`**: componente reutilizable que **recorre el sitio del cliente sin salir del nuestro**. Una captura de página completa vive dentro de un marco con barra de navegador y se traslada conforme bajas; el teléfono se le superpone en una esquina y avanza a otra velocidad, porque sincronizados se leen mecánicos y desfasados dan profundidad. Sirve las capturas con `<picture>` en cadena de respaldo **AVIF → WebP → PNG**: el navegador se queda con el primer formato que entienda (642 KB en AVIF frente a 3.3 MB del PNG de reserva).
351+
- **`src/data/i18n.ts`** y **`src/data/locales/{es,en}.ts`**: sección `projects` en el esquema y en ambos idiomas, con el slug localizado de la página índice (`/proyectos/` · `/projects/`).
352+
353+
### Cambiado
354+
355+
- **`src/components/pages/Home/Home.astro`**: la sección `#projects` deja de ser una cáscara con escena reservada y pasa a montar el caso destacado; la escena del sol viaja ahora dentro del propio componente.
356+
357+
### Técnico
358+
359+
- El recorrido de las capturas se ata **al marco**, no a la sección: vale 0 justo cuando el marco asoma en pantalla. Atarlo a la sección —mucho más alta y con el marco a media altura— hacía que al llegar a verlo el progreso fuera ya del 45 %, y el visitante se perdía la cabecera del sitio capturado. Además el recorrido se **limita en unidades de viewport**, porque estas capturas son larguísimas (la móvil ronda 1:19) y recorrerlas enteras las hacía pasar volando: con el tope, la velocidad queda por debajo de 1× respecto al scroll. Que no se llegue al pie es aceptable; perderse la cabecera, no.
360+
- El contraste sobre la banda de fuego se resuelve **apartando el sol** en móvil (se corre a estribor y encoge) y con un velo oscuro bajo la tarjeta, no con sombras detrás del texto.
361+
- Cabecera y caso comparten una sola rejilla en escritorio, de modo que la tarjeta arranca a la altura del titular sin márgenes negativos que se descuadren al cambiar el texto. Desde 90rem la tarjeta se lleva algo más de ancho (6/5 en vez de 7/5) para que las cuatro cifras entren en una sola fila.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ctg-code",
3-
"version": "0.19.0",
3+
"version": "0.20.0",
44
"dependencies": {
55
"@astrojs/sitemap": "^3.7.3",
66
"@fontsource-variable/inter": "^5.2.8",
642 KB
Binary file not shown.
3.24 MB
Loading
1.06 MB
Loading
952 KB
Binary file not shown.
3.06 MB
Loading
887 KB
Loading

src/components/pages/Home/Home.astro

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import LangNotice from "../../global/LangNotice/LangNotice.astro";
1616
import About from "../../sections/About/About.astro";
1717
import CTA from "../../ui/CTA/CTA.astro";
1818
import Starfield from "../../backdrop/Starfield/Starfield.astro";
19-
import Sun from "../../backdrop/Sun/Sun.astro";
19+
import Projects from "../../sections/Projects/Projects.astro";
2020
import Waves from "../../backdrop/Waves/Waves.astro";
2121
import CompassRose from "../../backdrop/CompassRose/CompassRose.astro";
2222
import { locales } from "../../../data/i18n";
@@ -181,12 +181,9 @@ const codeLines = t.hero.code.map((line) => tokenize(line));
181181
<!-- 02 · ABOUT — el CIELO: cirros de atardecer (tono dusk) -->
182182
<About lang={lang} />
183183

184-
<!-- 03 · PROJECTS — el HORIZONTE: la banda de fuego, el sol midiéndose
185-
con el sextante. SOLO escena + altura reservada: el contenido del
186-
escaparate llegará después, sección por sección. No inventar copy. -->
187-
<section id="projects" class="horizon-stage" data-tone="horizon">
188-
<Sun />
189-
</section>
184+
<!-- 03 · PROJECTS — el HORIZONTE: la banda de fuego y el escaparate. El
185+
caso destacado monta su propia escena (el sol con su sextante). -->
186+
<Projects lang={lang} />
190187

191188
<!-- 04 · SERVICES — el MAR: la hondura, olas a contracorriente y la rosa
192189
de los vientos como marca de agua. SOLO escena + altura reservada. -->
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
/**
3+
* Projects — el escaparate (sección del Home · tono horizon).
4+
*
5+
* Con un solo trabajo entregable, la pieza NO es una rejilla esperando
6+
* compañía: es un caso destacado a ancho completo. El sitio del cliente se
7+
* recorre solo dentro de su marco (<ScrollShot />) conforme bajas, y el
8+
* argumento son cifras verificables por cualquiera, no adjetivos.
9+
*
10+
* Dos salidas deliberadas: el sitio en producción del cliente (prueba) y la
11+
* página que engloba todo el trabajo (continuidad).
12+
*/
13+
import { locales, localizedPath } from "../../../data/i18n";
14+
import ScrollShot from "../../ui/ScrollShot/ScrollShot.astro";
15+
import Sun from "../../backdrop/Sun/Sun.astro";
16+
import "./Projects.css";
17+
18+
/* Capturas de página completa del sitio del cliente, en los tres formatos de
19+
la cadena de respaldo (AVIF → WebP → PNG). Ya vienen optimizadas: se
20+
importan como assets y se emiten tal cual. */
21+
import deskAvif from "../../../assets/recograscolombia_escritorio.avif";
22+
import deskWebp from "../../../assets/recograscolombia_escritorio.webp";
23+
import deskPng from "../../../assets/recograscolombia_escritorio.png";
24+
import mobAvif from "../../../assets/recograscolombia_movil.avif";
25+
import mobWebp from "../../../assets/recograscolombia_movil.webp";
26+
import mobPng from "../../../assets/recograscolombia_movil.png";
27+
28+
interface Props {
29+
lang: keyof typeof locales;
30+
}
31+
32+
const { lang } = Astro.props;
33+
const t = locales[lang].projects;
34+
const p = t.featured;
35+
36+
const desktopShot = { avif: deskAvif, webp: deskWebp, png: deskPng };
37+
const mobileShot = { avif: mobAvif, webp: mobWebp, png: mobPng };
38+
---
39+
40+
<section
41+
id="projects"
42+
class="projects horizon-stage"
43+
data-tone="horizon"
44+
aria-labelledby="projects-title"
45+
>
46+
<Sun />
47+
48+
<div class="pj-wrap">
49+
<header class="pj-head">
50+
<p class="eyebrow pj-eyebrow">
51+
<span class="pj-idx">02</span>
52+
<span class="pj-sep">/</span>
53+
{t.eyebrow}
54+
</p>
55+
<h2 id="projects-title" class="pj-title">{t.title}</h2>
56+
<p class="pj-lead">{t.lead}</p>
57+
</header>
58+
59+
<article class="pj-case">
60+
<div class="pj-shot">
61+
<ScrollShot
62+
desktop={desktopShot}
63+
mobile={mobileShot}
64+
alt={`${p.client} — ${p.tagline}`}
65+
url={p.urlLabel}
66+
/>
67+
68+
<!-- La prueba humana ANTES de la visual: la voz de quien pagó el trabajo
69+
encabeza el caso y las capturas lo sustentan. La cita va RECORTADA
70+
(con elipsis) sobre el original de Google; se transcribe a mano a
71+
propósito, no por API. -->
72+
<figure class="pj-quote">
73+
<p class="pj-quote-label">{t.testimonial.label}</p>
74+
<blockquote
75+
class="pj-quote-text"
76+
cite={t.testimonial.sourceUrl || undefined}
77+
>
78+
{t.testimonial.quote}
79+
</blockquote>
80+
81+
<figcaption class="pj-quote-by">
82+
<span class="pj-quote-author">{t.testimonial.author}</span>
83+
<span class="pj-quote-source">
84+
{
85+
t.testimonial.sourceUrl ? (
86+
<a
87+
href={t.testimonial.sourceUrl}
88+
target="_blank"
89+
rel="noopener noreferrer nofollow"
90+
>
91+
{t.testimonial.source}
92+
</a>
93+
) : (
94+
t.testimonial.source
95+
)
96+
}
97+
</span>
98+
</figcaption>
99+
100+
{t.testimonial.note && <p class="pj-quote-note">{t.testimonial.note}</p>}
101+
</figure>
102+
</div>
103+
104+
<div class="pj-body">
105+
<p class="pj-tagline">{p.tagline}</p>
106+
<h3 class="pj-client">{p.client}</h3>
107+
<p class="pj-summary">{p.summary}</p>
108+
109+
<ul class="pj-stack" role="list">
110+
{p.stack.map((tech) => <li class="pj-tech">{tech}</li>)}
111+
</ul>
112+
113+
<!-- Las cifras: la prueba dura, con su procedencia declarada -->
114+
<dl class="pj-metrics">
115+
{
116+
p.metrics.map((m) => (
117+
<div class="pj-metric">
118+
<dt class="pj-metric-label">{m.label}</dt>
119+
<dd class="pj-metric-value">{m.value}</dd>
120+
</div>
121+
))
122+
}
123+
</dl>
124+
<p class="pj-source">{t.metricsSource}</p>
125+
126+
<div class="pj-actions">
127+
<a
128+
class="pj-visit"
129+
href={p.url}
130+
target="_blank"
131+
rel="noopener noreferrer"
132+
>
133+
{t.visit}
134+
<span class="pj-visit-arrow" aria-hidden="true">↗</span>
135+
</a>
136+
137+
<a class="pj-all" href={localizedPath(lang, t.indexSlug)}>
138+
{t.ctaAll}
139+
<span class="pj-all-arrow" aria-hidden="true">→</span>
140+
</a>
141+
</div>
142+
</div>
143+
</article>
144+
</div>
145+
</section>

0 commit comments

Comments
 (0)