Back to index
Live · Case study 005

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

1985

Fundado

C/ Castellón, 11 — Moncofa

3

Empresas

Transquique · Drenajes · Secoman

7

Páginas seed

Bootstrap idempotente

96

Frames hero scrub

frame-001..096.jpg

16

Componentes editoriales

Cards · marquees · figures · stats

4

Templates PHP

page-empresa · grupo · contacto · landing2

2

Páginas fuera de menú

landing2 con vídeo scrub

0

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.

  1. 01

    Bedrock sobre WP vanilla

    Core (web/wp/) y plugins fuera del repo, gestionados por composer. .env por entorno en config/environments/{development,staging}.php. Document root acotado a web/ — postura de seguridad superior al WP clásico, repo limpio, plugins versionados como dependencias.

  2. 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.php bootstrapea el tema y delega a inc/setup.php, inc/enqueue.php, inc/templates.php, inc/pages-bootstrap.php. Plantillas como front-page.php, page-empresa.php, partials reutilizables en template-parts/.

  3. 03

    Vite 6 + Tailwind v4 (sin webpack ni laravel-mix)

    Pipeline mínimo: un único entry assets/src/js/main.js → manifest a assets/dist/.vite/manifest.json. Tailwind v4 vía @tailwindcss/vite sin tailwind.config.js clásico — todo el sistema en @theme {} dentro del CSS. Plugin custom gq-dev-host-writer escribe la URL del dev server en un archivo que PHP detecta para HMR.

  4. 04

    Bootstrap idempotente versionado

    Constante GQ_CONTENT_VERSION en inc/pages-bootstrap.php. Al activar el tema → crea las 7 páginas seed si faltan, asigna front-page y crea el menú "Principal". En cada init (prio 20), si la versión almacenada en option('gq_content_version') < GQ_CONTENT_VERSION → reescribe post_content y meta de las páginas seed. Trade-off asumido: el copy editorial vive en código, no en wp-admin.

  5. 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=N y rutas como /landing2 caerían al fallback de home. No reescribe .htaccess — lo gestiona el server.

  6. 06

    Datos por empresa en post meta

    Las 3 fichas (/transquique, /drenajes-moncofa, /secoman) usan la misma plantilla page-empresa.php con 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.

  1. 01

    Capítulos numerados como una revista

    Toda la web está estructurada como editorial impreso: 00 · Vídeo Hero01 · Manifiesto07 · CTA. Eyebrows con cuadradito 7×7 brand + texto mono 11px tracking 0.22em uppercase. Convierte el scroll en una lectura.

  2. 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 sobre radial-gradient que da textura sin verse.

  3. 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 con font-feature-settings: "ss01" on, "cv11" on. Geist Mono para eyebrows, ticker, índices con tracking 0.22em.

  4. 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 romano MCMLXXXV (1985). Decisión deliberada: ahorra "logo pendiente" y mantiene coherencia con todo el sistema.

  5. 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 con backdrop-filter: blur(4px). Aspect ratios bloqueados → cero CLS.

  6. 06

    Marquees CSS-only

    Tres marquees: ticker negro de header (48s), servicios bg-ink text-paper con separador rojo (60s), y clientes en doble pista contrarias (38s/44s) con mask-image: linear-gradient en los extremos para fade en lugar de corte duro. Cero JS — sólo @keyframes linear infinite e items duplicados.

  7. 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 cada raf.

  8. 08

    Reveals on-scroll · 8 variantes

    IntersectionObserver con up · down · left · right · zoom · scale · blur · rise. data-reveal-stagger aplica delays incrementales 0/90/180...ms a hijos. Stagger automático entre elementos en la misma "fila" del viewport agrupando por Math.round(rect.top / 60). prefers-reduced-motionis-visible instantáneo.

04

Pages.

7 rutas seed · template-driven

Slug
Template
Notas
/
front-page.php
Home con vídeo hero, manifiesto, animación 3D del Kenworth, empresas, marquee de servicios, contador, CTA
/grupo
page-grupo.php
El Grupo: cita de origen, cifras, 3 pilares, 4 promesas, factor humano
/transquique
page-empresa.php
Ficha de empresa — meta-driven (Transportes 1985)
/drenajes-moncofa
page-empresa.php
Ficha de empresa — meta-driven (Drenajes 2006)
/secoman
page-empresa.php
Ficha de empresa — meta-driven (Contenedores 2018)
/contacto
page-contacto.php
Datos, horario, formulario maquetado
/landing2
page-landing2.php
Variante con hero scrub por scroll (96 frames) — fuera de menú

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

Ver pieza interactiva en /frontend

07

Trade-offs.

Decisiones y deuda consciente

  1. 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.

  2. 02

    landing2 fuera del menú

    Variante con hero de vídeo scrub (96 fotogramas reproducidos por scroll) deliberadamente NO incluida en $order de gq_bootstrap_menu. Se mantiene como playground del cliente para A/B sin entrar en navegación pública.

  3. 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.

  4. 04

    Email/teléfono hardcodeados

    Deuda consciente: quique@transquique.com y 964 580 026 viven en varios partials. Refactor a opciones de WP cuando el cliente confirme canales definitivos. Las páginas legales (Aviso, Privacidad, Cookies) están con href="#" 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)

End of case study

¿Una marca con
40 años que contar?