initial commit
This commit is contained in:
commit
3ba76b4f02
90 changed files with 12507 additions and 0 deletions
145
docs/design-system/ui_kits/home/HeroZone.jsx
Normal file
145
docs/design-system/ui_kits/home/HeroZone.jsx
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// HeroZone — the reserved interactive hero. For now a constrained placeholder:
|
||||
// a chamfer-clipped panel of warm chiaroscuro whose light source drifts toward
|
||||
// the pointer (the one signature motion moment), with the ink stroke parallaxing
|
||||
// and the watermark beneath. A toggle reveals the zone spec + safe area so any
|
||||
// future interactive element inherits the same frame.
|
||||
const ASSET = '../../assets';
|
||||
|
||||
function HeroZone() {
|
||||
const [p, setP] = React.useState({ x: 0.62, y: 0.28 });
|
||||
const [showSpec, setShowSpec] = React.useState(false);
|
||||
const ref = React.useRef(null);
|
||||
const reduce = React.useRef(
|
||||
typeof matchMedia !== 'undefined' &&
|
||||
matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
);
|
||||
|
||||
function onMove(e) {
|
||||
if (reduce.current) return;
|
||||
const r = ref.current.getBoundingClientRect();
|
||||
setP({
|
||||
x: Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)),
|
||||
y: Math.min(1, Math.max(0, (e.clientY - r.top) / r.height)),
|
||||
});
|
||||
}
|
||||
function onLeave() {
|
||||
setP({ x: 0.62, y: 0.28 });
|
||||
}
|
||||
|
||||
// light follows pointer; ink drifts opposite for depth (short travel).
|
||||
const lx = 30 + p.x * 50;
|
||||
const ly = 8 + p.y * 40;
|
||||
const inkShift = `translate(${(0.5 - p.x) * 26}px, ${(0.5 - p.y) * 16}px)`;
|
||||
|
||||
const hz = {
|
||||
frame: {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
aspectRatio: '4 / 5',
|
||||
borderRadius: 'var(--radius-cutout)',
|
||||
background: `radial-gradient(120% 95% at ${lx}% ${ly}%, #C9B299 0%, #8A6A47 30%, #4A3826 60%, #221E18 92%)`,
|
||||
transition: 'background 700ms var(--ease-out)',
|
||||
overflow: 'hidden',
|
||||
cursor: 'crosshair',
|
||||
boxShadow: 'var(--shadow-2)',
|
||||
},
|
||||
accent: {
|
||||
position: 'absolute',
|
||||
right: '9%',
|
||||
top: '12%',
|
||||
width: '76px',
|
||||
opacity: 0.16,
|
||||
filter: 'invert(1)',
|
||||
transform: inkShift,
|
||||
transition: 'transform 900ms var(--ease-out)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
grain: {
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
backgroundImage: `url(${ASSET}/texture-grain.svg)`,
|
||||
backgroundSize: '220px',
|
||||
opacity: 0.12,
|
||||
mixBlendMode: 'overlay',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
watermark: {
|
||||
position: 'absolute',
|
||||
left: '8%',
|
||||
bottom: '8%',
|
||||
width: '128px',
|
||||
opacity: 0.1,
|
||||
filter: 'invert(1)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
toggle: {
|
||||
position: 'absolute',
|
||||
right: '8%',
|
||||
bottom: '7%',
|
||||
fontFamily: 'var(--font-mono)',
|
||||
fontSize: '10px',
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'rgba(239,231,218,0.55)',
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(239,231,218,0.22)',
|
||||
borderRadius: '2px',
|
||||
padding: '5px 9px',
|
||||
cursor: 'pointer',
|
||||
zIndex: 3,
|
||||
},
|
||||
safe: {
|
||||
position: 'absolute',
|
||||
inset: '9% 9% 9% 9%',
|
||||
border: '1px dashed rgba(239,231,218,0.4)',
|
||||
pointerEvents: 'none',
|
||||
display: showSpec ? 'block' : 'none',
|
||||
},
|
||||
safeLabel: {
|
||||
position: 'absolute',
|
||||
top: '-9px',
|
||||
left: '10px',
|
||||
background: '#221E18',
|
||||
padding: '0 6px',
|
||||
fontFamily: 'var(--font-mono)',
|
||||
fontSize: '9px',
|
||||
letterSpacing: '0.1em',
|
||||
color: 'rgba(239,231,218,0.7)',
|
||||
},
|
||||
placeholderTag: {
|
||||
position: 'absolute',
|
||||
left: '7%',
|
||||
top: '8%',
|
||||
fontFamily: 'var(--font-mono)',
|
||||
fontSize: '10px',
|
||||
letterSpacing: '0.14em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'rgba(239,231,218,0.45)',
|
||||
display: showSpec ? 'block' : 'none',
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={hz.frame}
|
||||
onMouseMove={onMove}
|
||||
onMouseLeave={onLeave}
|
||||
>
|
||||
<img src={`${ASSET}/accent-ringdots.svg`} style={hz.accent} alt="" />
|
||||
<div style={hz.grain} />
|
||||
<span style={hz.placeholderTag}>Hero zone — interactive · TBD</span>
|
||||
<div style={hz.safe}>
|
||||
<span style={hz.safeLabel}>SAFE AREA · 1:1.25 · pad 9%</span>
|
||||
</div>
|
||||
<img src={`${ASSET}/mark-rings.svg`} style={hz.watermark} alt="" />
|
||||
<button
|
||||
style={hz.toggle}
|
||||
onClick={() => setShowSpec((s) => !s)}
|
||||
>
|
||||
{showSpec ? 'Hide spec' : 'Zone spec'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Object.assign(window, { HeroZone });
|
||||
53
docs/design-system/ui_kits/home/LatestLine.jsx
Normal file
53
docs/design-system/ui_kits/home/LatestLine.jsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// LatestLine — the most recent post as ONE typographic line. Bait to move into
|
||||
// the site. No panel, no thumbnail. Hairline rule + arrow that drifts on hover.
|
||||
function LatestLine({ onDark = false }) {
|
||||
const [hover, setHover] = React.useState(false);
|
||||
const c = onDark
|
||||
? { fg: 'var(--fg-on-dark-1)', meta: 'var(--fg-on-dark-3)', rule: 'var(--hairline-on-dark)' }
|
||||
: { fg: 'var(--fg-1)', meta: 'var(--fg-3)', rule: 'var(--hairline)' };
|
||||
|
||||
const ll = {
|
||||
eyebrow: {
|
||||
fontFamily: 'var(--font-body)', fontWeight: 500, fontSize: '11px',
|
||||
letterSpacing: '0.22em', textTransform: 'uppercase',
|
||||
color: onDark ? 'var(--fg-on-dark-2)' : 'var(--fg-3)', margin: '0 0 14px',
|
||||
},
|
||||
line: {
|
||||
display: 'flex', alignItems: 'baseline', gap: '16px',
|
||||
textDecoration: 'none', color: c.fg,
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'var(--font-display)', fontWeight: 500,
|
||||
fontSize: 'clamp(20px, 2.2vw, 27px)', letterSpacing: '-0.01em',
|
||||
},
|
||||
meta: {
|
||||
fontFamily: 'var(--font-body)', fontSize: '12.5px', color: c.meta,
|
||||
fontFeatureSettings: '"onum" 1', marginLeft: 'auto', whiteSpace: 'nowrap',
|
||||
},
|
||||
arrow: {
|
||||
color: 'var(--accent-deep)',
|
||||
transform: hover ? 'translateX(5px)' : 'translateX(0)',
|
||||
transition: 'transform var(--dur-base) var(--ease-out)',
|
||||
},
|
||||
rule: { height: '1px', background: c.rule, marginTop: '14px' },
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p style={ll.eyebrow}>Selected writing</p>
|
||||
<a
|
||||
href="#"
|
||||
style={ll.line}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<span style={ll.title}>Notes on warm light and cold water</span>
|
||||
<span style={ll.meta}>Mar 2026</span>
|
||||
<span style={ll.arrow}>→</span>
|
||||
</a>
|
||||
<div style={ll.rule} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Object.assign(window, { LatestLine });
|
||||
41
docs/design-system/ui_kits/home/README.md
Normal file
41
docs/design-system/ui_kits/home/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# UI Kit — Home
|
||||
|
||||
A high-fidelity recreation of the **joshbairstow.com** home page: a minimal hub
|
||||
that *fuses* its three jobs into one composition rather than stacking three
|
||||
sections.
|
||||
|
||||
- **Interactive hero** (`HeroZone.jsx`) — the reserved zone, shown as a
|
||||
constrained placeholder: a **rounded** cut-out panel of warm chiaroscuro whose
|
||||
light source drifts toward the pointer (the one signature motion moment),
|
||||
with the **rings watermark** and a ring-dots accent. A **Zone spec** toggle
|
||||
reveals the safe area (9% pad, 1:1.25) so any future element inherits the
|
||||
frame. Respects `prefers-reduced-motion`.
|
||||
- **Latest-writing line** (`LatestLine.jsx`) — the newest post as *one*
|
||||
typographic line; bait, not a billboard. Arrow drifts on hover.
|
||||
- **Subdomain index** (`SubdomainIndex.jsx`) — navigation as a refined index;
|
||||
tolerates a growing/uneven set; a not-yet-live item shows a quiet `soon`
|
||||
badge, never looks broken. Hover indents the row + draws an ochre tick.
|
||||
- **Wordmark** (`Wordmark.jsx`) — Bodoni lockup; the only color event is a
|
||||
single ochre period.
|
||||
- **Light / dark** — **dark is the default** (deep warm ink ground); the header
|
||||
toggle swaps to the bone alternate. Grain, the rings watermark, and simple
|
||||
accent marks (rings/dots/pipes) sprinkled loosely carry through both.
|
||||
|
||||
## Run
|
||||
Open `index.html`. React + Babel load from CDN; components are separate
|
||||
`.jsx` files exported to `window`. Tokens come from `../../colors_and_type.css`;
|
||||
marks/texture from `../../assets/`.
|
||||
|
||||
## Files
|
||||
| File | Component |
|
||||
|---|---|
|
||||
| `index.html` | App shell, theming, layout grid |
|
||||
| `Wordmark.jsx` | Name lockup |
|
||||
| `HeroZone.jsx` | Interactive hero placeholder |
|
||||
| `LatestLine.jsx` | Latest-post line |
|
||||
| `SubdomainIndex.jsx` | Refined subdomain index nav |
|
||||
|
||||
## Notes
|
||||
This is a **cosmetic recreation** for prototyping — interactions are faked
|
||||
(links are inert). The hero's interactive element is a deliberate placeholder
|
||||
per the brief; drop the real piece into the same clipped frame + safe area.
|
||||
79
docs/design-system/ui_kits/home/SubdomainIndex.jsx
Normal file
79
docs/design-system/ui_kits/home/SubdomainIndex.jsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// 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 });
|
||||
25
docs/design-system/ui_kits/home/Wordmark.jsx
Normal file
25
docs/design-system/ui_kits/home/Wordmark.jsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Wordmark — the name lockup. Bodoni display, a single ochre period as the
|
||||
// only color event. Sits at the top-left of the composition.
|
||||
function Wordmark({ size = 'header', onDark = false }) {
|
||||
const isHeader = size === 'header';
|
||||
const wmStyles = {
|
||||
name: {
|
||||
fontFamily: 'var(--font-display)',
|
||||
fontWeight: 500,
|
||||
fontSize: isHeader ? '21px' : 'clamp(56px, 9vw, 132px)',
|
||||
lineHeight: isHeader ? 1 : 0.92,
|
||||
letterSpacing: isHeader ? '0' : '-0.02em',
|
||||
color: onDark ? 'var(--fg-on-dark-1)' : 'var(--fg-1)',
|
||||
margin: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
dot: { color: 'var(--accent-deep)' },
|
||||
};
|
||||
return (
|
||||
<h1 style={wmStyles.name}>
|
||||
Josh Bairstow<span style={wmStyles.dot}>.</span>
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
Object.assign(window, { Wordmark });
|
||||
230
docs/design-system/ui_kits/home/index.html
Normal file
230
docs/design-system/ui_kits/home/index.html
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Josh Bairstow</title>
|
||||
<link rel="stylesheet" href="../../colors_and_type.css">
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; }
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background-color: var(--ground-dark);
|
||||
color: var(--fg-1);
|
||||
min-height: 100vh;
|
||||
}
|
||||
body.dark { background-color: var(--ground-dark); }
|
||||
body:not(.dark) { background-color: var(--ground); }
|
||||
|
||||
/* page-wide grain greeble — static, barely-there (§5) */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed; inset: 0;
|
||||
background-image: url(../../assets/texture-grain.svg);
|
||||
background-size: 240px;
|
||||
opacity: 0.045;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
body.dark::before { opacity: 0.06; mix-blend-mode: screen; }
|
||||
|
||||
/* sprinkled accents — simple crisp marks (rings, dots, pipes) placed with
|
||||
loose, non-rigid positioning across the page. Quiet but present. */
|
||||
.mark {
|
||||
position: fixed; pointer-events: none; z-index: 0;
|
||||
filter: invert(52%) sepia(16%) saturate(340%) brightness(88%);
|
||||
opacity: 0.16;
|
||||
}
|
||||
body:not(.dark) .mark { filter: invert(34%) sepia(18%) saturate(280%) brightness(76%); opacity: 0.11; }
|
||||
.mark.m1 { width: 84px; top: 15%; left: 3%; transform: rotate(-5deg); }
|
||||
.mark.m2 { width: 28px; top: 72%; left: 6.5%; transform: rotate(8deg); }
|
||||
.mark.m3 { width: 50px; top: 28%; right: 3.5%; }
|
||||
.mark.m4 { width: 40px; bottom: 8%; right: 8%; }
|
||||
.mark.m5 { width: 66px; bottom: 18%; left: 45%; transform: rotate(-3deg); }
|
||||
|
||||
#root { position: relative; z-index: 1; }
|
||||
|
||||
.stage {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: clamp(28px, 5vh, 56px) clamp(24px, 5vw, 64px) clamp(32px, 5vh, 56px);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* header */
|
||||
.topbar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding-bottom: 18px;
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
}
|
||||
.dark .topbar { border-color: var(--hairline-on-dark); }
|
||||
.topmeta { display: flex; align-items: center; gap: 22px; }
|
||||
.coord {
|
||||
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em;
|
||||
color: var(--fg-3); font-feature-settings: "onum" 1;
|
||||
}
|
||||
.toggle {
|
||||
font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.14em;
|
||||
text-transform: uppercase; cursor: pointer;
|
||||
background: transparent; color: var(--fg-2);
|
||||
border: 1px solid var(--hairline-strong);
|
||||
border-radius: var(--radius-xs); padding: 6px 11px;
|
||||
transition: border-color var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
|
||||
}
|
||||
.toggle:hover { border-color: var(--fg-1); color: var(--fg-1); }
|
||||
.dark .toggle { color: var(--fg-on-dark-2); border-color: var(--hairline-on-dark); }
|
||||
.dark .toggle:hover { color: var(--fg-on-dark-1); border-color: var(--fg-on-dark-1); }
|
||||
|
||||
/* hero composition grid */
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 0.85fr;
|
||||
gap: clamp(32px, 6vw, 80px);
|
||||
align-items: stretch;
|
||||
padding: clamp(36px, 7vh, 84px) 0 clamp(28px, 5vh, 56px);
|
||||
flex: 1;
|
||||
}
|
||||
.heroLeft { display: flex; flex-direction: column; }
|
||||
.eyebrow {
|
||||
font-family: var(--font-body); font-weight: 500; font-size: 11px;
|
||||
letter-spacing: 0.24em; text-transform: uppercase; color: var(--fg-3);
|
||||
margin: 0 0 clamp(20px, 4vh, 34px);
|
||||
}
|
||||
.dark .eyebrow { color: var(--fg-on-dark-2); }
|
||||
.statement {
|
||||
font-family: var(--font-display); font-weight: 500;
|
||||
font-size: clamp(40px, 6.4vw, 92px); line-height: 0.96;
|
||||
letter-spacing: -0.02em; margin: 0; color: var(--fg-1);
|
||||
text-wrap: balance;
|
||||
}
|
||||
.dark .statement { color: var(--fg-on-dark-1); }
|
||||
.statement em { font-style: italic; font-weight: 400; }
|
||||
.statement .accent { color: var(--accent-deep); }
|
||||
.leadline {
|
||||
font-family: var(--font-body); font-weight: 300;
|
||||
font-size: clamp(15px, 1.5vw, 18px); line-height: 1.6;
|
||||
color: var(--fg-2); max-width: 42ch;
|
||||
margin: clamp(22px, 4vh, 34px) 0 0;
|
||||
}
|
||||
.dark .leadline { color: var(--fg-on-dark-2); }
|
||||
.heroLeftFoot { margin-top: auto; padding-top: clamp(28px, 5vh, 48px); }
|
||||
|
||||
.heroRight { display: flex; align-items: stretch; }
|
||||
.heroRight > * { width: 100%; align-self: center; }
|
||||
|
||||
/* lower band: index + signature */
|
||||
.lower {
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 0.85fr;
|
||||
gap: clamp(32px, 6vw, 80px) clamp(32px, 6vw, 80px);
|
||||
align-items: end;
|
||||
padding-top: clamp(22px, 3.5vh, 36px);
|
||||
border-top: 1px solid var(--hairline);
|
||||
}
|
||||
.dark .lower { border-top-color: var(--hairline-on-dark); }
|
||||
.lowerInk {
|
||||
grid-column: 1 / -1; width: 50px; opacity: 0.22;
|
||||
margin: 0 0 clamp(6px, 1.4vh, 12px);
|
||||
filter: invert(52%) sepia(16%) saturate(340%) brightness(88%);
|
||||
}
|
||||
body:not(.dark) .lowerInk { filter: invert(34%) sepia(18%) saturate(280%) brightness(76%); }
|
||||
.indexHead {
|
||||
font-family: var(--font-body); font-weight: 500; font-size: 11px;
|
||||
letter-spacing: 0.24em; text-transform: uppercase; color: var(--fg-3);
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
.dark .indexHead { color: var(--fg-on-dark-2); }
|
||||
.sigBlock { display: flex; flex-direction: column; align-items: flex-end; gap: 12px; }
|
||||
.sigBlock img { width: 74px; opacity: 0.24;
|
||||
filter: invert(52%) sepia(16%) saturate(340%) brightness(88%); }
|
||||
body:not(.dark) .sigBlock img { filter: invert(34%) sepia(18%) saturate(280%) brightness(76%); }
|
||||
.copyline {
|
||||
font-family: var(--font-body); font-size: 12px; color: var(--fg-3);
|
||||
font-feature-settings: "onum" 1; text-align: right;
|
||||
}
|
||||
.dark .copyline { color: var(--fg-on-dark-3); }
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.hero, .lower { grid-template-columns: 1fr; }
|
||||
.heroRight { max-width: 420px; }
|
||||
.sigBlock { align-items: flex-start; }
|
||||
.copyline { text-align: left; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="dark">
|
||||
<img class="mark m1" src="../../assets/accent-ringdots.svg" alt="">
|
||||
<img class="mark m2" src="../../assets/accent-pipes.svg" alt="">
|
||||
<img class="mark m3" src="../../assets/accent-dots.svg" alt="">
|
||||
<img class="mark m4" src="../../assets/accent-concentric.svg" alt="">
|
||||
<img class="mark m5" src="../../assets/accent-ringdots.svg" alt="">
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
||||
|
||||
<script type="text/babel" src="Wordmark.jsx"></script>
|
||||
<script type="text/babel" src="HeroZone.jsx"></script>
|
||||
<script type="text/babel" src="LatestLine.jsx"></script>
|
||||
<script type="text/babel" src="SubdomainIndex.jsx"></script>
|
||||
|
||||
<script type="text/babel">
|
||||
function App() {
|
||||
const [dark, setDark] = React.useState(true);
|
||||
React.useEffect(() => {
|
||||
document.body.classList.toggle('dark', dark);
|
||||
}, [dark]);
|
||||
|
||||
return (
|
||||
<div className="stage">
|
||||
<header className="topbar">
|
||||
<Wordmark size="header" onDark={dark} />
|
||||
<div className="topmeta">
|
||||
<span className="coord">33.7969° S · Sydney</span>
|
||||
<button className="toggle" onClick={() => setDark(d => !d)}>
|
||||
{dark ? 'Light' : 'Dark'}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="hero">
|
||||
<div className="heroLeft">
|
||||
<p className="eyebrow">Engineer · Sydney</p>
|
||||
<h2 className="statement">
|
||||
I make small,<br /><em>careful</em> things<span className="accent">.</span>
|
||||
</h2>
|
||||
<p className="leadline">
|
||||
Software, tools, and the occasional mark in ink. A quiet corner of the
|
||||
internet — mostly writing, sometimes shipping.
|
||||
</p>
|
||||
<div className="heroLeftFoot">
|
||||
<LatestLine onDark={dark} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="heroRight">
|
||||
<HeroZone />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="lower">
|
||||
<img className="lowerInk" src="../../assets/accent-dots.svg" alt="" />
|
||||
<div>
|
||||
<p className="indexHead">Index</p>
|
||||
<SubdomainIndex onDark={dark} />
|
||||
</div>
|
||||
<div className="sigBlock">
|
||||
<img src="../../assets/accent-ringdots.svg" alt="" />
|
||||
<span className="copyline">© Josh Bairstow — Sydney</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue