mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
The distribution's track and runners-up, the switches and the surface read in theme inks, so they hold on a light ground. The hero's sentence takes a size that tracks the width on phones, keeping "don't generate text." on one line at 375. The harness board says what it is: Kai and Jev on Decision Index v2. evidence_sufficient is evidence_enough, so the graph's label clears its value. Co-authored-by: Hanzo Dev <[email protected]>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* The hero's sentence, said in the order it is read: the condition, the thing
|
|
* not to do, and then the one word that is the page. Each line rises into place
|
|
* a beat after the last; the last word lands on a rule drawn under it.
|
|
*
|
|
* The words are in the markup from the first byte, a space after each line so
|
|
* the heading reads as one sentence to a crawler and a screen reader. The motion module grounds
|
|
* every start state, so a reader without script, or who declines motion, reads
|
|
* the whole sentence where it belongs.
|
|
*/
|
|
|
|
import { Text } from '@hanzo/ui'
|
|
import { motion } from '@/components/motion'
|
|
|
|
const RISE = { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 } }
|
|
|
|
const EASE = [0.22, 1, 0.36, 1] as const
|
|
|
|
/**
|
|
* On a phone the hero's size would break "don't generate text." after its first
|
|
* word; a size that tracks the width keeps each line of the sentence whole down
|
|
* to 375, and each line balances if it must wrap.
|
|
*/
|
|
const PHONE = { '$platform-web': { fontSize: 'clamp(2rem, 9vw, 2.75rem)', lineHeight: '1.1em', textWrap: 'balance' } } as const
|
|
|
|
export default function Headline({ lines, last }: { lines: readonly string[]; last: string }) {
|
|
const beat = 0.1 * lines.length
|
|
return (
|
|
<>
|
|
{lines.map((l, i) => (
|
|
<Text key={l} render={<motion.span {...RISE} transition={{ duration: 0.5, ease: EASE, delay: 0.1 * i }} />} display="block" $max-sm={PHONE}>
|
|
{l}{' '}
|
|
</Text>
|
|
))}
|
|
<Text
|
|
render={<motion.span {...RISE} transition={{ duration: 0.45, ease: EASE, delay: beat + 0.2 }} />}
|
|
position="relative"
|
|
mt="$2"
|
|
$platform-web={{ display: 'inline-block' }}
|
|
$max-sm={PHONE}
|
|
>
|
|
{last}
|
|
{/* The rule grows from the left under the word; undrawn, it is an ornament missing, not a word. */}
|
|
<Text
|
|
render={<motion.span aria-hidden initial={{ scaleX: 0 }} animate={{ scaleX: 1 }} transition={{ duration: 0.6, ease: EASE, delay: beat + 0.45 }} />}
|
|
position="absolute"
|
|
l={0}
|
|
r={0}
|
|
b={-3}
|
|
height={3}
|
|
bg="$ink"
|
|
$platform-web={{ display: 'block', transformOrigin: 'left' }}
|
|
/>
|
|
</Text>
|
|
</>
|
|
)
|
|
}
|