// PROYECTOS — 9 proyectos reales de Joaquina, imágenes locales en uploads/.
// Cada proyecto tiene cover + galería completa para futuras vistas detalle.
// Las cards FLOTAN como los objetos del moodboard: velocidades constantes,
// rebotan contra los bordes del contenedor, todas con tamaño estándar.

const PROJECTS = [
  {
    id: 'pl-001',
    year: '2025',
    cat: 'Trend forecasting',
    title: 'Civic Hope',
    subtitle: 'Trend Forecasting · Futuro Consumidor',
    client: 'Investigación de tendencias',
    tags: ['Trend forecasting','Tendencia 2027','Diseño de estéticas'],
    description: 'Si decidiéramos reciclar la ira social y colectiva en accionar benéfico, logrando despresurizar tensiones mundiales que afectan de manera global a las sociedades alrededor del planeta, lograríamos entonces un consumidor satisfecho y con voz.\n\nEl orgullo herido por la inflación, así como las tensiones nacionales y geopolíticas, han dejado a muchos hartos de la política y la economía, y la ira está llenando ese vacío como un sentimiento unificador. Macro-angrynomics es entonces el estado actual en el que se desarrollan las sociedades, ante el desencuentro de la vida cotidiana y los discursos políticos y sociales.\n\nLa necesidad de transigir estas dinámicas cambiantes y de refundir los matices múltiples son bisagras hacia el bienestar comunitario. La despresurización social se presenta como método de alivio y liberación ante la tensión y la rabia.\n\nEn 2027, la migración global seguirá redefiniendo las fronteras culturales, económicas y ecológicas. A medida que muchos gobiernos se vuelven cada vez más conservadores, las marcas asumirán la responsabilidad de adoptar este cambio como un recurso valioso para las empresas y la sociedad.\n\nFrente a este fenómeno, el nexo de los extremos produce la unión, de los polos al centro. Enfocarse en cómo se conduce el activismo hostil y marginado hacia encuadres mutables y colaborativos. La conciencia cultural, el cuidado y el respeto serán clave.',
    folder: 'uploads/Tren Forecasting -Civic Hope',
    cover: 'CivicHope_03.jpg',
    gallery: ['CivicHope_03.jpg','CivicHope_02.jpg','CivicHope_01.jpg','CivicHope_04.jpg','CivicHope_05.jpg','CivicHope_06.jpg'],
    size: 'tall'
  },
  {
    id: 'pl-002',
    year: '2025',
    cat: 'Investigación estética',
    title: 'Mundo Post-Vínculo',
    subtitle: 'Escenario futuro · Comportamiento social',
    client: 'Ensayo visual — Autopoiesis',
    tags: ['Conceptual','Social','Futuro'],
    description: 'Tema trabajado: Autopoiesis (modulación de uno mismo).\n\nEn este escenario futuro, el humano ha perdido no solo el deseo de vincularse, sino también la capacidad fisiológica de interacción. La ciencia, adaptándose a esta nueva condición, diseñó artefactos que no protegen ni aíslan desde la contención, sino desde la expansión simbólica del rechazo.\n\nEl collar (pieza principal) es uno de esos dispositivos. Conformado por protuberancias punzantes, semi-flexibles y distribuidas de manera irregular, actúa como un campo táctil de repulsión constante, impidiendo cualquier intento de cercanía. Sus filamentos se proyectan desde el cuerpo como extensiones defensivas que garantizan una zona segura, no por control, sino por incapacidad afectiva.\n\nEl vínculo se volvió ruido. El otro, amenaza. La ciencia no respondió con encierro, sino con libertad blindada. Sin muros, sin algoritmos, sin sistemas de control. Solo cuerpos autónomos que portan en su superficie el límite.\n\nLa arquitectura ya no se construye en el espacio. Se diseña sobre el cuerpo. Aparecen así los dispositivos de repulsión relacional: formas que no protegen ni decoran, sino que excluyen de forma activa y silenciosa. El mundo ya no necesita organización social, porque la relación dejó de ser deseada. Los espacios ya no albergan vínculos: son entornos abiertos y oxigenados… pero vacíos y fríos.\n\nEl humano se mueve en soledad, sin conflicto, sin lenguaje, sin contacto. Y en ese entorno silencioso, el diseño se vuelve el único lenguaje visible de rechazo.',
    folder: 'uploads/Escenario futuro - Comportamiento social',
    cover: 'PostVinculo_07.jpg',
    gallery: ['PostVinculo_07.jpg','PostVinculo_02.jpg','PostVinculo_03.jpg','PostVinculo_05.jpg','PostVinculo_06.jpg','PostVinculo_01.jpg','PostVinculo_08.jpg'],
    size: 'tall'
  },
  {
    id: 'pl-003',
    year: '2025',
    cat: 'Dirección de campaña',
    title: 'Miamee',
    subtitle: 'La Meca del Idealismo — Soccer Beach',
    client: 'Campaña editorial',
    tags: ['Campaña','Idealismo','Estereotipos'],
    description: 'SOCCER BEACH.\n\nTras la investigación de los estereotipos argentinos sobre la ciudad de Miami, y sobre cómo aquellas personas que viven allí, en su mayoría, buscan pertenecer. Condensamos una estética estereotipada que pone al deporte en el foco de investigación, y cómo Miami puede leerse como “meca” de un deporte idealizado, donde la presencia suma más puntos que el gol.',
    folder: 'uploads/Direccion de campana',
    cover: 'Miamee_03.jpg',
    gallery: ['Miamee_03.jpg','Miamee_04.jpg','Miamee_05.jpg','Miamee_06.jpg','Miamee_07.jpg'],
    size: 'square'
  },
  {
    id: 'pl-004',
    year: '2024',
    cat: 'Trend forecasting',
    title: 'Preadolescentes',
    subtitle: 'Diseño Editorial · Trend Research',
    client: 'Laboratorio académico',
    tags: ['Editorial','Lab','Sistema'],
    description: 'Proyecto universitario para laboratorio de tendencias. Detección de tendencias para el sector preadolescente.\n\nProyecto con fines académicos — LABORATORIO DE TENDENCIAS. Diseño editorial para investigación sobre los pre adolescentes, con la intención de detectar una tendencia en hábitos de consumo futuro.\n\nDiseño editorial: Enriquez Elisabeth.\nEquipo: Albornoz Joaquina, Bolia Mora, Mazzuchi Candela.',
    folder: 'uploads/Trend Forecasting',
    cover: 'DisenoEditorial_Tendencias_02.png',
    gallery: [
      'DisenoEditorial_Tendencias_02.png','DisenoEditorial_Tendencias_04.png',
      'DisenoEditorial_Tendencias_05.png','DisenoEditorial_Tendencias_06.png',
      'DisenoEditorial_Tendencias_07.png','DisenoEditorial_Tendencias_08.png',
      'DisenoEditorial_Tendencias_09.png','DisenoEditorial_Tendencias_10.png',
      'DisenoEditorial_Tendencias_11.png','DisenoEditorial_Tendencias_12.png',
      'DisenoEditorial_Tendencias_13.png','DisenoEditorial_Tendencias_16.png',
      'DisenoEditorial_Tendencias_20.png','DisenoEditorial_Tendencias_21.png',
      'DisenoEditorial_Tendencias_22.png','DisenoEditorial_Tendencias_23.png',
      'DisenoEditorial_Tendencias_26.png','DisenoEditorial_Tendencias_27.png'
    ],
    size: 'wide'
  },
  {
    id: 'pl-005',
    year: '2025',
    cat: 'Editorial',
    title: 'Nota Periodística',
    subtitle: 'Pieza editorial · Dirección de producción',
    client: 'Revista — Casa del Teatro',
    tags: ['Editorial','Dirección de producción'],
    description: 'Nota a Santiago Rios. En la misma trabajamos la imagen como un segundo lenguaje que acompaña sus vivencias y el tono de la misma. Una imagen fría pero contrastada a su expresión de entusiasmo, refleja la adversidad en la vida del actor, y su habilidad y talento para poder afrontarla con entusiasmo.\n\nTrabajo junto a: Mora Bolia, Elisabeth Enriquez, Candela Mazzuchi.',
    folder: 'uploads/Nota Periodistica',
    cover: 'NotaPeriodistica_01.jpg',
    gallery: ['NotaPeriodistica_01.jpg','NotaPeriodistica_02.jpg','NotaPeriodistica_04.jpg','NotaPeriodistica_05.jpg','NotaPeriodistica_07.jpg'],
    size: 'square'
  },
  {
    id: 'pl-006',
    year: '2024',
    cat: 'Trend forecasting',
    title: 'Génesis Gustativa',
    subtitle: 'Gastronomía · Tendencias 2030',
    client: 'Informe de tendencias 2030',
    tags: ['Gastronomía','Diseño editorial','Tendencias 2030'],
    description: 'Diseño reformulación de la patisserie. Análisis sociocultural actual y trend forecasting.\n\nSe plantea un futuro innovador donde la arquitectura influye en este ámbito gastronómico, logrando así dimensiones distintas, y materialidades que, reconectando con las raíces de los ingredientes, nos muestran un nuevo camino. Como todo en nuestra cotidianidad, va a ir mutando hacia enfoques más naturales y de conexión, es así que incluso la pastelería logrará comunicar desde las raíces.\n\nProducto final: Albornoz Joaquina y Mora Bolia.\nFotografía: Mora Bolia\nDesarrollo conceptual: Albornoz Joaquina, Mora Bolia, Candela Mazzuchi, Elizabeth Enríquez.',
    folder: 'uploads/Genesis Gustativa',
    cover: 'GenesisGustativa_01.png',
    gallery: ['GenesisGustativa_01.png','GenesisGustativa_03.png','GenesisGustativa_04.png','GenesisGustativa_06.png','GenesisGustativa_08.png','GenesisGustativa_09.png','GenesisGustativa_10.png','GenesisGustativa_11.png','GenesisGustativa_12.png','GenesisGustativa_13.png','GenesisGustativa_14.png'],
    size: 'tall'
  },
  {
    id: 'pl-007',
    year: '2024',
    cat: 'Editorial',
    title: 'Bauhaus',
    subtitle: 'Diseño Editorial · Producto',
    client: 'Editorial — Bauhaus foto producto',
    tags: ['Editorial','Bauhaus','Producto'],
    description: 'Patrones históricos que moldearon el diseño durante años: utilidad sobre diseño y geometría sobre orgánico. Nos permite hoy comunicar sensualidad y feminidad de una manera estructurada pero sensible. Bauhaus foto producto.',
    folder: 'uploads/Editorial Producto',
    cover: 'DisenoEditorial_Bauhaus_01.png',
    gallery: ['DisenoEditorial_Bauhaus_01.png','DisenoEditorial_Bauhaus_02.jpeg','DisenoEditorial_Bauhaus_03.png','DisenoEditorial_Bauhaus_04.png'],
    size: 'square'
  },
  {
    id: 'pl-008',
    year: '2024',
    cat: 'Dirección de campaña',
    title: 'Delaostia',
    subtitle: 'Campaña de marca',
    client: 'Delaostia — campaña universitaria',
    tags: ['Campaña','Producción','Identidad'],
    description: 'Campaña identidad de marca para Delaostia. En un año donde la marca sufrió una reinvención y necesidad de adaptación al entorno, buscamos reavivar el espíritu rockstar que tanto la caracteriza, y así surgió nuestra campaña.\n\nCon sensualidad, diversión, juventud y exploración logramos con simples recursos reversionar la imagen de una campaña que parecía perdida en la sociedad cambiante.\n\nProducción: Mora Bolia, Candela Mazzuchi, Elisabeth Enríquez, Albornoz Joaquina.',
    folder: 'uploads/Direccion de Campana - Delaostia',
    cover: 'CampanaDelaostia_01.png',
    gallery: ['CampanaDelaostia_01.png','CampanaDelaostia_02.png','CampanaDelaostia_03.png'],
    size: 'square'
  },
  {
    id: 'pl-009',
    year: '2024',
    cat: 'Dirección de campaña',
    title: "70's Are the New Black",
    subtitle: 'Campaña Época',
    client: 'Editorial / campaña universitaria',
    tags: ['Campaña','70s','Estilo'],
    description: "Campaña de época desarrollada en torno a la reinterpretación de las siluetas femeninas de los 70s. La misma busca condensar los estereotipos de la indumentaria, pero con una visión contemporánea que irrumpe el lenguaje.\n\nUn trabajo de puesta en escena, locación, ambientación, estilismo y fotografía.\n\nProducción: Mora Bolia, Candela Mazzuchi, Elisabeth Enriquez, Albornoz Joaquina.",
    folder: 'uploads/Campana Epoca',
    cover: '70sAreNewBlack_01.jpg',
    gallery: ['70sAreNewBlack_01.jpg','70sAreNewBlack_02.jpg','70sAreNewBlack_03.jpg','70sAreNewBlack_04.jpg','70sAreNewBlack_05.jpg','70sAreNewBlack_06.jpg'],
    size: 'tall'
  }
];

// Helper to build the image URL — encodes folder + filename so spaces work
function imgUrl(p, file) {
  return p.folder.split('/').map(encodeURIComponent).join('/') + '/' + encodeURIComponent(file);
}

// Tamaño estándar de la card flotante (px). Misma proporción para todas.
const CARD_W = 320;
const CARD_H = 240;

function Proyectos() {
  const [filter, setFilter] = React.useState('Todos');
  const [hover, setHover] = React.useState(null);
  const [activeId, setActiveId] = React.useState(null);

  const cats = ['Todos','Dirección de campaña','Editorial',
                'Trend forecasting','Investigación estética'];

  const visible = filter === 'Todos' ? PROJECTS : PROJECTS.filter(p => p.cat === filter);

  const activeProject = activeId ? PROJECTS.find(p => p.id === activeId) : null;

  // Deep-link sync
  const slugOf = (p) => (p.slug ||
    p.title.toLowerCase()
      .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
      .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''));

  React.useEffect(() => {
    const apply = () => {
      const m = (window.location.hash || '').match(/^#proyecto\/([a-z0-9-]+)/i);
      if (m) {
        const slug = m[1].toLowerCase();
        const found = PROJECTS.find(p => slugOf(p) === slug);
        setActiveId(found ? found.id : null);
      } else {
        setActiveId(null);
      }
    };
    apply();
    window.addEventListener('hashchange', apply);
    return () => window.removeEventListener('hashchange', apply);
  }, []);

  const openProject = (id) => {
    const p = PROJECTS.find(p => p.id === id);
    if (!p) return;
    const newHash = '#proyecto/' + slugOf(p);
    if (window.location.hash !== newHash) {
      history.pushState(null, '', newHash);
    }
    setActiveId(id);
  };
  const closeProject = () => {
    if (window.location.hash.startsWith('#proyecto/')) {
      history.pushState(null, '', window.location.pathname + window.location.search + '#proyectos');
    }
    setActiveId(null);
  };
  const navWithHash = (dir) => {
    if (!activeProject) return;
    const idx = PROJECTS.findIndex(p => p.id === activeProject.id);
    const next = (idx + dir + PROJECTS.length) % PROJECTS.length;
    openProject(PROJECTS[next].id);
  };

  // ——— FLOATING CARDS ENGINE ———
  // Cada card mantiene posición (x,y) en px y velocidades (vx,vy) constantes.
  // Cuando un borde es alcanzado, la velocidad se invierte (rebote elástico).
  // Hover NO pausa, pero la card hovered sube en z-index.
  const floatRef = React.useRef(null);
  const cardRefs = React.useRef({});
  const motionRef = React.useRef({});  // id -> {x,y,vx,vy}
  const [containerSize, setContainerSize] = React.useState({ w: 1200, h: 720 });

  // Initialize motion state for any project that doesn't have one yet,
  // distributing initial positions across the container.
  const ensureMotion = React.useCallback((items, w, h) => {
    const m = motionRef.current;
    items.forEach((p, i) => {
      if (!m[p.id]) {
        // Pseudo-random distribution based on index — deterministic so SSR-like
        // first paint doesn't jitter, but staggered enough to look natural.
        const seed = i * 9301 + 49297;
        const rx = ((seed % 1000) / 1000);
        const ry = (((seed * 7) % 1000) / 1000);
        const ang = ((seed * 13) % 360) * Math.PI / 180;
        const speed = 22 + ((seed * 3) % 18);  // px/sec — velocidad constante
        m[p.id] = {
          x: rx * Math.max(1, w - CARD_W),
          y: ry * Math.max(1, h - CARD_H),
          vx: Math.cos(ang) * speed,
          vy: Math.sin(ang) * speed,
        };
      }
    });
  }, []);

  // Measure container & keep size up to date
  React.useEffect(() => {
    const measure = () => {
      const el = floatRef.current;
      if (!el) return;
      const r = el.getBoundingClientRect();
      setContainerSize({ w: r.width, h: r.height });
    };
    measure();
    window.addEventListener('resize', measure);
    return () => window.removeEventListener('resize', measure);
  }, []);

  // rAF loop — moves each visible card with constant velocity, bouncing on edges.
  React.useEffect(() => {
    ensureMotion(visible, containerSize.w, containerSize.h);

    let raf = 0;
    let last = performance.now();

    const tick = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000);
      last = now;

      const w = containerSize.w;
      const h = containerSize.h;
      if (w <= 0 || h <= 0) { raf = requestAnimationFrame(tick); return; }

      const maxX = Math.max(0, w - CARD_W);
      const maxY = Math.max(0, h - CARD_H);

      visible.forEach((p) => {
        const s = motionRef.current[p.id];
        if (!s) return;

        s.x += s.vx * dt;
        s.y += s.vy * dt;

        // Rebote contra los bordes — velocidad sigue siendo constante en módulo.
        if (s.x <= 0) { s.x = 0; s.vx = Math.abs(s.vx); }
        else if (s.x >= maxX) { s.x = maxX; s.vx = -Math.abs(s.vx); }
        if (s.y <= 0) { s.y = 0; s.vy = Math.abs(s.vy); }
        else if (s.y >= maxY) { s.y = maxY; s.vy = -Math.abs(s.vy); }

        const el = cardRefs.current[p.id];
        if (el) {
          el.style.transform = `translate3d(${s.x}px, ${s.y}px, 0)`;
        }
      });

      raf = requestAnimationFrame(tick);
    };

    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [visible, containerSize.w, containerSize.h, ensureMotion]);

  // Cuando cambia el filtro, reposiciona las cards visibles dentro del área
  // (las que estaban fuera podrían quedar atrapadas en un offset previo).
  React.useEffect(() => {
    const w = containerSize.w, h = containerSize.h;
    if (w <= 0 || h <= 0) return;
    const maxX = Math.max(0, w - CARD_W);
    const maxY = Math.max(0, h - CARD_H);
    visible.forEach((p, i) => {
      const s = motionRef.current[p.id];
      if (!s) return;
      if (s.x > maxX || s.y > maxY) {
        s.x = Math.min(s.x, maxX);
        s.y = Math.min(s.y, maxY);
      }
    });
  }, [filter, containerSize.w, containerSize.h, visible]);

  return (
    <section id="proyectos" data-screen-label="05 Proyectos" style={{
      position: 'relative', padding: '140px 48px 140px',
      background: 'var(--grey-paper)'
    }}>
      <PageNumber n={5} label="ARCHIVO" />
      <SectionLabel idx={4} title="Proyectos" kicker="Seleccionás el fragmento — queda nombrado." />

      {/* Filter bar */}
      <div style={{
        display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 36,
        borderTop: '1px solid var(--ink)', borderBottom: '1px solid var(--ink)',
        padding: '14px 0'
      }}>
        {cats.map(c => (
          <button key={c}
            onClick={() => setFilter(c)}
            style={{
              background: filter === c ? 'var(--red)' : 'transparent',
              color: filter === c ? '#fff' : 'var(--ink)',
              border: '1px solid ' + (filter === c ? 'var(--red)' : 'rgba(10,10,10,.25)'),
              padding: '7px 12px',
              fontSize: 11, letterSpacing: '0.2em', textTransform: 'uppercase',
              fontWeight: 500, cursor: 'pointer',
              fontFamily: 'var(--font-body)'
            }}>
            {c}
          </button>
        ))}
      </div>

      {/* Floating area — las cards flotan aquí, rebotando contra los bordes. */}
      <div ref={floatRef} style={{
        position: 'relative',
        width: '100%',
        height: '78vh',
        minHeight: 640,
        overflow: 'hidden',
        border: '1px solid rgba(10,10,10,.18)',
        background: 'var(--grey-paper)'
      }}>
        {visible.map((p, i) => (
          <FloatingFicha
            key={p.id}
            p={p}
            cardRef={(el) => { cardRefs.current[p.id] = el; }}
            isHover={hover === p.id}
            anyHover={hover !== null}
            onEnter={() => setHover(p.id)}
            onLeave={() => setHover(null)}
            onOpen={() => openProject(p.id)}
          />
        ))}
      </div>

      {/* Footer strip */}
      <div style={{
        marginTop: 40, paddingTop: 14, borderTop: '1px solid rgba(10,10,10,.25)',
        display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 14
      }}>
        <span className="eyebrow" style={{ opacity: .55 }}>
          {visible.length} / {PROJECTS.length} fichas · archivo flotante
        </span>
        <span className="eyebrow" style={{ color: 'var(--red)' }}>Seguir archivo →</span>
      </div>

      {activeProject && typeof ProjectDetail !== 'undefined' && (
        <ProjectDetail
          project={activeProject}
          onClose={closeProject}
          onNav={navWithHash}
        />
      )}
    </section>
  );
}

// Floating card — todas el mismo tamaño (CARD_W × CARD_H).
// Conserva el diseño original (badges, hover, título + tags).
function FloatingFicha({ p, cardRef, isHover, anyHover, onEnter, onLeave, onOpen }) {
  return (
    <article
      ref={cardRef}
      data-name={p.title}
      onMouseEnter={onEnter}
      onMouseLeave={onLeave}
      onClick={onOpen}
      role="button"
      tabIndex={0}
      onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpen && onOpen(); } }}
      style={{
        position: 'absolute',
        left: 0, top: 0,
        width: CARD_W,
        height: CARD_H,
        overflow: 'hidden',
        background: '#fff',
        border: '1px solid rgba(10,10,10,.12)',
        boxShadow: isHover
          ? '0 24px 48px rgba(0,0,0,.22)'
          : '0 8px 18px rgba(0,0,0,.10)',
        zIndex: isHover ? 50 : 1,
        opacity: anyHover && !isHover ? 0.78 : 1,
        transition: 'box-shadow .25s, opacity .25s',
        cursor: 'pointer',
        willChange: 'transform',
        userSelect: 'none'
      }}
    >
      <img src={imgUrl(p, p.cover)} alt={p.title} loading="lazy" draggable={false}
           style={{
             position: 'absolute', inset: 0, width: '100%', height: '100%',
             objectFit: 'cover',
             transition: 'transform .7s ease, opacity .4s',
             transform: isHover ? 'scale(1.05)' : 'scale(1)',
             opacity: isHover ? .7 : 1,
             pointerEvents: 'none'
           }}/>

      {/* ID badge top-left */}
      <span style={{
        position: 'absolute', top: 10, left: 10,
        background: 'rgba(255,255,255,.92)', color: 'var(--ink)',
        padding: '4px 8px',
        fontSize: 10, letterSpacing: '0.2em', textTransform: 'uppercase', fontWeight: 600
      }}>
        NMN-{p.id.toUpperCase()} · {p.year}
      </span>

      {/* Category top-right */}
      <span style={{
        position: 'absolute', top: 10, right: 10,
        background: 'var(--red)', color: '#fff',
        padding: '4px 8px',
        fontSize: 10, letterSpacing: '0.2em', textTransform: 'uppercase', fontWeight: 600
      }}>{p.cat}</span>

      {/* Title + meta bottom */}
      <div style={{
        position: 'absolute', left: 0, right: 0, bottom: 0,
        padding: '12px 14px 12px',
        background: isHover ? 'var(--ink)' : 'rgba(255,255,255,.94)',
        color: isHover ? '#fff' : 'var(--ink)',
        transition: 'background .25s, color .25s',
        display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end',
        gap: 10
      }}>
        <div style={{ minWidth: 0 }}>
          <div className="titular" style={{
            fontSize: 16, fontWeight: 600, lineHeight: 1.1,
            whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis'
          }}>
            {p.title}
          </div>
          <div style={{
            fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase',
            opacity: .65, marginTop: 3,
            whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis'
          }}>
            {p.client}
          </div>
        </div>
      </div>
    </article>
  );
}

Object.assign(window, { Proyectos, PROJECTS });
