05 — Editorial corporate · Bedrock + Three.js
Grupo
Quique.
Site corporativo para grupo familiar de transporte (40 años, 3 empresas) — tema PHP plano sobre Bedrock, bootstrap idempotente versionado, sistema visual completo tipo revista impresa y hero 3D del Kenworth con Three.js + DRACO.
- Role
- Full-stack engineer · Solo
- Year
- 2025 — ongoing
- Status
- Live · production
- Scope
- Bedrock · Theme · Pipeline · 3D
- Stack
- Bedrock · Vite 6 · Tailwind v4 · GSAP · Lenis · Three.js
- Sector
- Transporte · Excavaciones · Drenajes
Cifras del proyecto
Fundado
C/ Castellón, 11 — Moncofa
Empresas
Transquique · Drenajes · Secoman
Páginas seed
Bootstrap idempotente
Frames hero scrub
frame-001..096.jpg
Componentes editoriales
Cards · marquees · figures · stats
Templates PHP
page-empresa · grupo · contacto · landing2
Páginas fuera de menú
landing2 con vídeo scrub
Clicks en wp-admin
Activar tema = web completa
01
Architecture.
Bedrock + theme-as-installer
Composer manda el core. El tema es el instalador — al activarlo, las 7 páginas se crean solas, el menú se cablea a primary y footer y los permalinks pretty quedan forzados desde código.
- 01
Bedrock sobre WP vanilla
Core (
web/wp/) y plugins fuera del repo, gestionados porcomposer..envpor entorno enconfig/environments/{development,staging}.php. Document root acotado aweb/— postura de seguridad superior al WP clásico, repo limpio, plugins versionados como dependencias. - 02
Tema PHP plano (no Sage / no Blade)
Sage añadiría peso (Acorn, Blade) que no compensa para un site corporativo de 7 páginas.
functions.phpbootstrapea el tema y delega ainc/setup.php,inc/enqueue.php,inc/templates.php,inc/pages-bootstrap.php. Plantillas comofront-page.php,page-empresa.php, partials reutilizables entemplate-parts/. - 03
Vite 6 + Tailwind v4 (sin webpack ni laravel-mix)
Pipeline mínimo: un único entry
assets/src/js/main.js→ manifest aassets/dist/.vite/manifest.json. Tailwind v4 vía@tailwindcss/vitesintailwind.config.jsclásico — todo el sistema en@theme {}dentro del CSS. Plugin customgq-dev-host-writerescribe la URL del dev server en un archivo que PHP detecta para HMR. - 04
Bootstrap idempotente versionado
Constante
GQ_CONTENT_VERSIONeninc/pages-bootstrap.php. Al activar el tema → crea las 7 páginas seed si faltan, asigna front-page y crea el menú "Principal". En cadainit(prio 20), si la versión almacenada enoption('gq_content_version')<GQ_CONTENT_VERSION→ reescribepost_contentymetade las páginas seed. Trade-off asumido: el copy editorial vive en código, no en wp-admin. - 05
Permalinks pretty forzados
gq_ensure_pretty_permalinks()setea/%postname%/si el ajuste está vacío y flushea reglas. Sin esto, las páginas creadas por el bootstrap quedarían en?page_id=Ny rutas como/landing2caerían al fallback de home. No reescribe.htaccess— lo gestiona el server. - 06
Datos por empresa en post meta
Las 3 fichas (
/transquique,/drenajes-moncofa,/secoman) usan la misma plantillapage-empresa.phpcon datos en_gq_empresa_{key,tagline,year,color}. Renderizado idéntico, sólo cambian datos — añadir una empresa = una entrada en el array de definición.
02
Re-seed.
Versioned content bootstrap
inc/pages-bootstrap.php · GQ_CONTENT_VERSION-driven re-seed
define('GQ_CONTENT_VERSION', 3);
function gq_maybe_reseed_content(): void {
$stored = (int) get_option('gq_content_version', 0);
if ($stored >= GQ_CONTENT_VERSION) return;
foreach (gq_pages_definition() as $slug => $def) {
$page = get_page_by_path($slug);
if (!$page) {
// Crea las páginas que se hayan añadido a la definición
$page_id = wp_insert_post([
'post_title' => $def['title'],
'post_name' => $slug,
'post_status' => 'publish',
'post_type' => 'page',
'post_content' => $def['content'],
]);
} else {
// Reescribe content + meta de las páginas seed existentes
wp_update_post([
'ID' => $page->ID,
'post_content' => $def['content'],
]);
}
if (!empty($def['meta'])) {
foreach ($def['meta'] as $key => $value) {
update_post_meta($page->ID, $key, $value);
}
}
}
update_option('gq_content_version', GQ_CONTENT_VERSION);
}
add_action('init', 'gq_maybe_reseed_content', 20); Bumpear la constante = re-sincronizar copy editorial sin migración manual. Trade-off asumido: pisa edits del admin en las páginas seed.
03
Visual.
Editorial system · 16 components
Toda la web está estructurada como una revista impresa — capítulos numerados, eyebrows con cuadradito brand, italics rojas display como sello, y placeholders editoriales que evitan el look "incompleto" mientras llegan las fotos del cliente.
- 01
Capítulos numerados como una revista
Toda la web está estructurada como editorial impreso:
00 · Vídeo Hero→01 · Manifiesto→07 · CTA. Eyebrows con cuadradito 7×7 brand + texto mono 11px tracking 0.22em uppercase. Convierte el scroll en una lectura. - 02
Paleta editorial · rojo quirúrgico
#E30613(rojo Quique) sólo como acento — itálicas display, viñetas (◉ ▸ ✦ ↗), eyebrows, hover,::selection. Nunca en bloques planos. Fondo#F6F4EE(paper cálido) con patrón de puntos 4×4 px sobreradial-gradientque da textura sin verse. - 03
Tipografía display variable
Fraunces (variable:
opsz 9–144,SOFT 30–100,wght 300–700, ital) para display — italics 300 + brand → look revista. Manrope (300–700) para body confont-feature-settings: "ss01" on, "cv11" on. Geist Mono para eyebrows, ticker, índices con tracking 0.22em. - 04
Wordmark sin logo SVG
La marca es tipografía:
<span>Grupo</span><span class="gq-brand__amp">Q</span><span>uique</span>. La Q en rojo italic actúa de logotipo. Año en superíndice mono romanoMCMLXXXV(1985). Decisión deliberada: ahorra "logo pendiente" y mantiene coherencia con todo el sistema. - 05
Placeholders editoriales (gq-figure)
Mientras el cliente no entrega fotos, los huecos no parecen "incompletos": cada figura compone 3 capas — diagonales 135° tipo plano técnico + grano SVG
feTurbulence+ degradado vertical#262626 → #141414. Plus: número de figura cursiva enorme como marca de agua y chip esquina conbackdrop-filter: blur(4px). Aspect ratios bloqueados → cero CLS. - 06
Marquees CSS-only
Tres marquees: ticker negro de header (48s), servicios
bg-ink text-papercon separador✦rojo (60s), y clientes en doble pista contrarias (38s/44s) conmask-image: linear-gradienten los extremos para fade en lugar de corte duro. Cero JS — sólo@keyframes linear infinitee items duplicados. - 07
Smooth scroll selectivo (Lenis)
Lenis sólo se activa en
(hover: hover) and (min-width: 1024px) and (prefers-reduced-motion: no-preference). En touch/mobile →null(scroll nativo). Razón: iOS/Android tienen momentum nativo superior y Lenis + ScrollTrigger + hero 3D provoca stutters sistemáticos en iOS Safari. Sincronizado con GSAP ticker para que ScrollTrigger update en cadaraf. - 08
Reveals on-scroll · 8 variantes
IntersectionObserverconup · down · left · right · zoom · scale · blur · rise.data-reveal-staggeraplica delays incrementales 0/90/180...ms a hijos. Stagger automático entre elementos en la misma "fila" del viewport agrupando porMath.round(rect.top / 60).prefers-reduced-motion→is-visibleinstantáneo.
04
Pages.
7 rutas seed · template-driven
05
Components.
Sistema editorial completo
Layout / Tipografía
- gq-container (max 1320)
- gq-section ritmo editorial
- Eyebrow 7×7 brand
- Capítulos numerados
- Italics rojas display
- ::selection brand
Componentes
- Ticker industrial 48s
- Wordmark tipográfico
- Drawer móvil 100dvh
- Hero vídeo + chip
- Cards de empresa
- Stats counter GSAP
- Marquee servicios
- Editorial list
- gq-figure placeholder
Microinteracciones
- Underline directional
- Card rule scale-x 700ms
- WhatsApp pulse 2.4s
- Scroll hint scaleY
- Auto-hide header rAF
- cubic-bezier(.2,.7,.2,1)
06
Kenworth.
Three.js · DRACO · PBR
El hero del home es un Kenworth en Three.js renderizado con DRACO (3.9 MB), PBR + 4 SpotLights, faros y pilotos como targets emisivos. Misma pieza está expuesta en /frontend con OrbitControls — drag to rotate, auto-rotate al soltar.
Engine
Three.js r184
Geometry
GLB · DRACO
Lighting
PBR · 4 spots
Postprocess
UnrealBloom · 0.25
07
Trade-offs.
Decisiones y deuda consciente
- 01
Re-seed que pisa edits manuales
Si el cliente edita una página seed desde el admin y bumpeamos
GQ_CONTENT_VERSION, las edits se sobrescriben. Trade-off asumido — el copy editorial vive en código, no en wp-admin. - 02
landing2 fuera del menú
Variante con hero de vídeo scrub (96 fotogramas reproducidos por scroll) deliberadamente NO incluida en
$orderdegq_bootstrap_menu. Se mantiene como playground del cliente para A/B sin entrar en navegación pública. - 03
Hero 3D del Kenworth
Three.js + GLB + DRACO + PBR + 4 SpotLights. Mostrado en /frontend con OrbitControls (drag to rotate). Para case study: el modelo se sirve desde
assets/src/models/camionaco.glb(3.9 MB DRACO-compressed), faros y emissivos como targets para timeline. - 04
Email/teléfono hardcodeados
Deuda consciente:
quique@transquique.comy964 580 026viven en varios partials. Refactor a opciones de WP cuando el cliente confirme canales definitivos. Las páginas legales (Aviso, Privacidad, Cookies) están conhref="#"esperando textos.
08
Stack.
PHP corporativo · pipeline minimalista · 3D opcional
Backend
- PHP ≥ 8.3
- Composer
- Bedrock 1.30.1
- WordPress 6.9.4
- Roots/wordpress
Frontend
- Vite 6
- Tailwind v4
- @tailwindcss/vite
- Manifest-driven
- HMR via .vite-dev-host
Animación / 3D
- GSAP 3.12 + ScrollTrigger
- Lenis 1.1
- Three.js 0.169
- DRACO compression
- IntersectionObserver
Devtools
- Laravel Pint (lint)
- Pest (tests)
- .env por entorno
- php -S localhost:8080 -t web
- npm run dev (Vite 5173)