79 lines
2.7 KiB
JavaScript
79 lines
2.7 KiB
JavaScript
// SubdomainIndex — the navigation as a refined index, not a button farm.
|
|
// Tolerates a growing, uneven set; a "not yet live" item is quiet, not broken.
|
|
// Hover: row indents a touch and a hairline tick draws in (felt, not seen).
|
|
const SUBDOMAINS = [
|
|
{ n: '01', name: 'Blog', desc: 'Writing', live: true },
|
|
{ n: '02', name: 'Code', desc: 'Experiments & tools', live: true },
|
|
{ n: '03', name: 'Art', desc: 'Generative & physical', live: true },
|
|
{ n: '04', name: 'Signage', desc: 'Chalk on easel', live: true },
|
|
{ n: '05', name: 'Keycaps', desc: 'Mechanical', live: true },
|
|
{ n: '06', name: 'Social', desc: '', live: false },
|
|
];
|
|
|
|
function IndexRow({ item, onDark }) {
|
|
const [hover, setHover] = React.useState(false);
|
|
const live = item.live;
|
|
const c = onDark
|
|
? { fg: 'var(--fg-on-dark-1)', dim: 'var(--fg-on-dark-3)', rule: 'var(--hairline-on-dark)' }
|
|
: { fg: 'var(--fg-1)', dim: 'var(--fg-3)', rule: 'var(--hairline)' };
|
|
|
|
const row = {
|
|
display: 'flex', alignItems: 'baseline', gap: '16px',
|
|
textDecoration: 'none', padding: '13px 0',
|
|
borderTop: `1px solid ${c.rule}`,
|
|
paddingLeft: hover && live ? '10px' : '0',
|
|
transition: 'padding-left var(--dur-base) var(--ease-out)',
|
|
cursor: live ? 'pointer' : 'default',
|
|
};
|
|
const num = {
|
|
fontFamily: 'var(--font-mono)', fontSize: '10px',
|
|
color: c.dim, width: '24px', flex: 'none', fontFeatureSettings: '"onum" 1',
|
|
};
|
|
const name = {
|
|
fontFamily: 'var(--font-display)', fontWeight: 500,
|
|
fontSize: 'clamp(19px, 2vw, 24px)', letterSpacing: '0.01em',
|
|
color: live ? c.fg : c.dim,
|
|
};
|
|
const tick = {
|
|
width: hover && live ? '22px' : '0px', height: '1px',
|
|
background: 'var(--accent-deep)', alignSelf: 'center',
|
|
transition: 'width var(--dur-base) var(--ease-out)',
|
|
};
|
|
const desc = {
|
|
fontFamily: 'var(--font-body)', fontSize: '12px', color: c.dim,
|
|
marginLeft: 'auto',
|
|
};
|
|
const soon = {
|
|
fontFamily: 'var(--font-mono)', fontSize: '9.5px', letterSpacing: '0.12em',
|
|
textTransform: 'uppercase', color: c.dim, marginLeft: 'auto',
|
|
border: `1px solid ${c.rule}`, borderRadius: '2px', padding: '2px 7px',
|
|
};
|
|
|
|
return (
|
|
<a
|
|
href="#"
|
|
style={row}
|
|
onMouseEnter={() => setHover(true)}
|
|
onMouseLeave={() => setHover(false)}
|
|
onClick={(e) => e.preventDefault()}
|
|
>
|
|
<span style={num}>{item.n}</span>
|
|
<span style={name}>{item.name}</span>
|
|
<span style={tick} />
|
|
{live
|
|
? <span style={desc}>{item.desc}</span>
|
|
: <span style={soon}>soon</span>}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
function SubdomainIndex({ onDark = false }) {
|
|
return (
|
|
<nav>
|
|
{SUBDOMAINS.map((it) => (
|
|
<IndexRow key={it.n} item={it} onDark={onDark} />
|
|
))}
|
|
</nav>
|
|
);
|
|
}
|
|
Object.assign(window, { SubdomainIndex });
|