feat(estratega): marco line-art listo para el retrato real
This commit is contained in:
1 parent
3cb2b1f78b
commit
a2fa730b7b
3 files changed
+82
-9
No files matched your search
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { usePrefersReducedMotion } from '@/lib/motion';
|
||||||
|
|
||||||
|
// Line-art frame for the strategist's portrait. Built so it reads as
|
||||||
|
// deliberate with today's placeholder inside and accepts the real photograph
|
||||||
|
// when it arrives without any rework: the frame never assumes what it wraps.
|
||||||
|
//
|
||||||
|
// The gold arc traces roughly three quarters of the circle and draws itself in
|
||||||
|
// — an open frame, not a closed badge.
|
||||||
|
export function MarcoRetrato({ children, className = '' }: { children: ReactNode; className?: string }) {
|
||||||
|
const reduced = usePrefersReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`relative w-44 h-44 md:w-64 md:h-64 shrink-0 ${className}`}>
|
||||||
|
<svg viewBox="0 0 100 100" className="absolute -inset-3 w-[calc(100%+1.5rem)] h-[calc(100%+1.5rem)] text-dorado" aria-hidden="true" focusable="false">
|
||||||
|
{reduced ? (
|
||||||
|
<circle
|
||||||
|
cx={50}
|
||||||
|
cy={50}
|
||||||
|
r={47}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray="220 295"
|
||||||
|
transform="rotate(-120 50 50)"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<motion.circle
|
||||||
|
cx={50}
|
||||||
|
cy={50}
|
||||||
|
r={47}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray="220 295"
|
||||||
|
transform="rotate(-120 50 50)"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
whileInView={{ pathLength: 1, opacity: 1 }}
|
||||||
|
viewport={{ once: true, amount: 0.4 }}
|
||||||
|
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
<div className="relative w-full h-full rounded-full overflow-hidden">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { MarcoRetrato } from '../MarcoRetrato';
|
||||||
|
|
||||||
|
describe('<MarcoRetrato />', () => {
|
||||||
|
it('renders whatever portrait it is given', () => {
|
||||||
|
render(<MarcoRetrato><span>retrato</span></MarcoRetrato>);
|
||||||
|
expect(screen.getByText('retrato')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the frame decorative: the portrait carries the meaning', () => {
|
||||||
|
const { container } = render(<MarcoRetrato><span>retrato</span></MarcoRetrato>);
|
||||||
|
expect(container.querySelector('svg')).toHaveAttribute('aria-hidden', 'true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('draws the arc that will survive the real photograph landing', () => {
|
||||||
|
const { container } = render(<MarcoRetrato><span>retrato</span></MarcoRetrato>);
|
||||||
|
expect(container.querySelectorAll('circle').length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@ import { Container } from '@/components/primitives/Container';
|
|||||||
import { PatronPuntos } from '@/components/primitives/PatronPuntos';
|
import { PatronPuntos } from '@/components/primitives/PatronPuntos';
|
||||||
import { Wordmark } from '@/components/primitives/Wordmark';
|
import { Wordmark } from '@/components/primitives/Wordmark';
|
||||||
import { ContadorNumero } from '@/components/primitives/ContadorNumero';
|
import { ContadorNumero } from '@/components/primitives/ContadorNumero';
|
||||||
|
import { MarcoRetrato } from '@/components/primitives/MarcoRetrato';
|
||||||
import { estratega } from '@/lib/content';
|
import { estratega } from '@/lib/content';
|
||||||
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
||||||
|
|
||||||
@@ -14,15 +15,15 @@ export function Estratega() {
|
|||||||
<Container className="py-24 md:py-32">
|
<Container className="py-24 md:py-32">
|
||||||
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="grid gap-12 md:grid-cols-[auto_1fr] md:items-center">
|
className="grid gap-12 md:grid-cols-[auto_1fr] md:items-center">
|
||||||
{/* TODO dueña: real portrait. Until then, a branded placeholder built
|
{/* TODO dueña: real portrait. The frame is built to take it as-is —
|
||||||
from the manual's dot pattern rather than a stand-in stock photo. */}
|
drop an <Image fill /> in place of the placeholder below. */}
|
||||||
<motion.div variants={fadeInUp}
|
<motion.div variants={fadeInUp}>
|
||||||
className="w-44 h-44 md:w-64 md:h-64 rounded-full flex-shrink-0 overflow-hidden relative bg-azul-profundo grid place-items-center ring-4 ring-dorado ring-offset-4"
|
<MarcoRetrato>
|
||||||
aria-label="Retrato de la estratega — pendiente"
|
<div className="w-full h-full bg-azul-profundo grid place-items-center" role="img" aria-label="Retrato de la estratega — pendiente">
|
||||||
role="img"
|
<PatronPuntos rows={4} cols={7} className="absolute inset-0 m-auto w-[150%] h-auto text-white/15" />
|
||||||
>
|
<span className="relative"><Wordmark size="sm" onDark /></span>
|
||||||
<PatronPuntos rows={4} cols={7} className="absolute inset-0 m-auto w-[150%] h-auto text-white/15" />
|
</div>
|
||||||
<span className="relative"><Wordmark size="sm" onDark /></span>
|
</MarcoRetrato>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<div>
|
<div>
|
||||||
<motion.p variants={fadeInUp} className="text-xs tracking-[0.3em] uppercase font-black text-dorado">
|
<motion.p variants={fadeInUp} className="text-xs tracking-[0.3em] uppercase font-black text-dorado">
|
||||||
|
|||||||
Reference in new issue
Block a user