feat: binary glow effects, refined drawings and calls to action
Dark surfaces now carry light: frames glow along their edge and shed binary digits (BitAura), dark sections open with a lit seam (BitSeam), the career path is a line of light and "My work" became a dark stage. Buttons carry a travelling light and a spot that follows the pointer (BitGlow). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+314
-21
@@ -321,26 +321,6 @@
|
||||
|
||||
/* Additional global styles from BaseLayout */
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
/* Selection color */
|
||||
::selection {
|
||||
background: #0f172a;
|
||||
@@ -1079,8 +1059,72 @@ pre:has(code[class*="language-"]) {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
/* Light that follows the pointer on a `data-glow` surface (see BitGlow). */
|
||||
.bit-glow {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 400ms ease;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
[data-glow]:hover > .bit-glow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
/* Dark surfaces: the dot grid lights up around the pointer. */
|
||||
.bit-glow-dots {
|
||||
background-image:
|
||||
radial-gradient(
|
||||
320px circle at var(--gx, 50%) var(--gy, 50%),
|
||||
rgba(148, 163, 184, 0.22),
|
||||
transparent 70%
|
||||
),
|
||||
radial-gradient(#e2e8f0 1.25px, transparent 1.25px);
|
||||
background-size:
|
||||
auto,
|
||||
20px 20px;
|
||||
-webkit-mask-image: radial-gradient(
|
||||
280px circle at var(--gx, 50%) var(--gy, 50%),
|
||||
#000,
|
||||
transparent 75%
|
||||
);
|
||||
mask-image: radial-gradient(
|
||||
280px circle at var(--gx, 50%) var(--gy, 50%),
|
||||
#000,
|
||||
transparent 75%
|
||||
);
|
||||
}
|
||||
/* Cards: only the border lights up, in the card's text colour. */
|
||||
.bit-glow-edge {
|
||||
inset: -1px;
|
||||
padding: 2px;
|
||||
background: radial-gradient(
|
||||
320px circle at var(--gx, 50%) var(--gy, 50%),
|
||||
currentColor,
|
||||
transparent 70%
|
||||
);
|
||||
-webkit-mask:
|
||||
linear-gradient(#000 0 0) content-box,
|
||||
linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#000 0 0) content-box exclude,
|
||||
linear-gradient(#000 0 0);
|
||||
}
|
||||
/* Dark buttons: a spot of light under the pointer. */
|
||||
.bit-glow-spot {
|
||||
background: radial-gradient(
|
||||
90px circle at var(--gx, 50%) var(--gy, 50%),
|
||||
rgba(129, 140, 248, 0.5),
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.bit-burst {
|
||||
.bit-burst,
|
||||
.bit-glow {
|
||||
display: none;
|
||||
}
|
||||
.bit-card::before,
|
||||
@@ -1088,3 +1132,252 @@ pre:has(code[class*="language-"]) {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls to action: a light travels round the edge of the pill and glows softly
|
||||
* behind it; on hover the glow comes up. `bit-cta-light` is for white pills on
|
||||
* dark surfaces, `bit-cta-quiet` only lights up on hover.
|
||||
*/
|
||||
@property --cta-angle {
|
||||
syntax: "<angle>";
|
||||
inherits: false;
|
||||
initial-value: 0deg;
|
||||
}
|
||||
.bit-cta {
|
||||
--cta-a: rgba(129, 140, 248, 0.95);
|
||||
--cta-b: #ffffff;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
}
|
||||
.bit-cta-light {
|
||||
--cta-a: rgba(99, 102, 241, 0.95);
|
||||
--cta-b: #312e81;
|
||||
}
|
||||
.bit-cta-quiet {
|
||||
--cta-a: rgba(99, 102, 241, 0.9);
|
||||
--cta-b: #0f172a;
|
||||
}
|
||||
.bit-cta::before,
|
||||
.bit-cta::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
background: conic-gradient(
|
||||
from var(--cta-angle),
|
||||
transparent 0 58%,
|
||||
var(--cta-a) 76%,
|
||||
var(--cta-b) 86%,
|
||||
var(--cta-a) 93%,
|
||||
transparent 100%
|
||||
);
|
||||
animation: bit-cta-turn 6s linear infinite;
|
||||
}
|
||||
/* The edge itself. */
|
||||
.bit-cta::before {
|
||||
inset: -1px;
|
||||
padding: 1.5px;
|
||||
-webkit-mask:
|
||||
linear-gradient(#000 0 0) content-box,
|
||||
linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#000 0 0) content-box exclude,
|
||||
linear-gradient(#000 0 0);
|
||||
}
|
||||
/* The glow behind the edge. */
|
||||
.bit-cta::after {
|
||||
inset: -2px;
|
||||
z-index: -1;
|
||||
padding: 6px;
|
||||
-webkit-mask:
|
||||
linear-gradient(#000 0 0) content-box,
|
||||
linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#000 0 0) content-box exclude,
|
||||
linear-gradient(#000 0 0);
|
||||
filter: blur(10px);
|
||||
opacity: 0.55;
|
||||
transition: opacity 300ms ease;
|
||||
}
|
||||
.bit-cta-quiet::before,
|
||||
.bit-cta-quiet::after {
|
||||
opacity: 0;
|
||||
transition: opacity 300ms ease;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.bit-cta:hover::after,
|
||||
.group:hover > .bit-cta::after {
|
||||
opacity: 1;
|
||||
}
|
||||
.bit-cta-quiet:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
.bit-cta-quiet:hover::after {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
@keyframes bit-cta-turn {
|
||||
to {
|
||||
--cta-angle: 360deg;
|
||||
}
|
||||
}
|
||||
|
||||
/* Slow light drifting over a dark picture (see PortraitSection). */
|
||||
.bit-aurora {
|
||||
position: absolute;
|
||||
inset: -10%;
|
||||
pointer-events: none;
|
||||
mix-blend-mode: screen;
|
||||
background:
|
||||
radial-gradient(
|
||||
38% 46% at 38% 42%,
|
||||
rgba(99, 102, 241, 0.55),
|
||||
transparent 70%
|
||||
),
|
||||
radial-gradient(
|
||||
30% 40% at 64% 72%,
|
||||
rgba(129, 140, 248, 0.35),
|
||||
transparent 70%
|
||||
);
|
||||
filter: blur(40px);
|
||||
animation: bit-aurora-drift 16s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes bit-aurora-drift {
|
||||
from {
|
||||
transform: translate3d(-4%, -3%, 0) scale(1);
|
||||
}
|
||||
to {
|
||||
transform: translate3d(5%, 4%, 0) scale(1.12);
|
||||
}
|
||||
}
|
||||
|
||||
/* The current station on the career line: a node that breathes light. */
|
||||
.bit-node-live {
|
||||
animation: bit-node-breathe 3.2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes bit-node-breathe {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow:
|
||||
0 0 0 4px rgba(129, 140, 248, 0.22),
|
||||
0 0 18px 2px rgba(129, 140, 248, 0.55);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
0 0 0 8px rgba(129, 140, 248, 0.12),
|
||||
0 0 34px 8px rgba(129, 140, 248, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Native feel: the page should behave like a well-made app on every input type.
|
||||
*/
|
||||
html {
|
||||
color-scheme: light;
|
||||
/* Anchor jumps stop below the fixed header. */
|
||||
scroll-padding-top: 6rem;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
accent-color: #0f172a;
|
||||
caret-color: #0f172a;
|
||||
}
|
||||
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
font-kerning: normal;
|
||||
}
|
||||
|
||||
/* Headlines break into even lines, running text avoids lonely last words. */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
text-wrap: balance;
|
||||
}
|
||||
p,
|
||||
li,
|
||||
dd,
|
||||
summary {
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
/* Controls react at once to a tap and are not selected like text. */
|
||||
a,
|
||||
button,
|
||||
summary,
|
||||
[role="tab"],
|
||||
[role="button"] {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
button,
|
||||
summary,
|
||||
[role="tab"],
|
||||
[role="button"],
|
||||
a[href^="#"] {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Decoration is neither selectable nor draggable. */
|
||||
[aria-hidden="true"],
|
||||
canvas,
|
||||
img,
|
||||
svg {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
img,
|
||||
svg,
|
||||
a {
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
/* One visible keyboard focus, none for mouse and touch. */
|
||||
:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
:focus-visible {
|
||||
outline: 2px solid #0f172a;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
a:focus-visible,
|
||||
button:focus-visible,
|
||||
summary:focus-visible {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* A short press-in on anything that can be pressed. */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
button:active,
|
||||
[role="tab"]:active,
|
||||
a[class*="rounded-full"]:active,
|
||||
a:active > [class*="rounded-full"] {
|
||||
scale: 0.97;
|
||||
transition-duration: 80ms;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark surfaces: light selection, light focus ring, dark system controls. */
|
||||
.bg-slate-900,
|
||||
.bg-slate-950 {
|
||||
color-scheme: dark;
|
||||
}
|
||||
.bg-slate-900 ::selection,
|
||||
.bg-slate-950 ::selection {
|
||||
background: #f8fafc;
|
||||
color: #0f172a;
|
||||
}
|
||||
.bg-slate-900 :focus-visible,
|
||||
.bg-slate-950 :focus-visible {
|
||||
outline-color: #f8fafc;
|
||||
}
|
||||
|
||||
/* Fixed controls stay clear of the home indicator and rounded corners. */
|
||||
@supports (padding: env(safe-area-inset-bottom)) {
|
||||
body {
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
import type { Metadata } from "next";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Inter, JetBrains_Mono, Newsreader } from "next/font/google";
|
||||
import { Analytics } from "@/src/components/Analytics";
|
||||
import { BitBurst } from "@/src/components/bits/BitBurst";
|
||||
import { BitGlow } from "@/src/components/bits/BitGlow";
|
||||
import { Footer } from "@/src/components/Footer";
|
||||
import { Header } from "@/src/components/Header";
|
||||
import { InteractiveElements } from "@/src/components/InteractiveElements";
|
||||
import {
|
||||
INDEXING,
|
||||
SITE_DESCRIPTION,
|
||||
SITE_LANGUAGE,
|
||||
SITE_NAME,
|
||||
SITE_TITLE,
|
||||
SITE_URL,
|
||||
} from "@/src/content/site";
|
||||
import { robotsMeta } from "@/src/domain/indexing";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
|
||||
// A real monospaced fallback has almost the same letter width. The default
|
||||
// fallback (a resized Arial) wraps the mono labels differently, so the hero
|
||||
// jumped when the font arrived.
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-jetbrains-mono",
|
||||
display: "swap",
|
||||
adjustFontFallback: false,
|
||||
fallback: ["ui-monospace", "Menlo", "Consolas", "monospace"],
|
||||
});
|
||||
const newsreader = Newsreader({
|
||||
subsets: ["latin"],
|
||||
@@ -36,6 +45,7 @@ export const metadata: Metadata = {
|
||||
applicationName: SITE_NAME,
|
||||
authors: [{ name: SITE_NAME, url: SITE_URL }],
|
||||
creator: SITE_NAME,
|
||||
robots: robotsMeta(INDEXING),
|
||||
icons: {
|
||||
icon: "/favicon.svg",
|
||||
apple: "/apple-touch-icon.png",
|
||||
@@ -56,6 +66,12 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#ffffff",
|
||||
colorScheme: "light",
|
||||
viewportFit: "cover",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
@@ -63,7 +79,7 @@ export default function RootLayout({
|
||||
}) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
lang={SITE_LANGUAGE}
|
||||
className={`${inter.variable} ${newsreader.variable} ${jetbrainsMono.variable}`}
|
||||
>
|
||||
<body className="min-h-screen bg-white">
|
||||
@@ -72,6 +88,7 @@ export default function RootLayout({
|
||||
<Footer />
|
||||
<InteractiveElements />
|
||||
<BitBurst />
|
||||
<BitGlow />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Metadata } from "next";
|
||||
import { BitAura } from "@/src/components/bits/BitAura";
|
||||
import { DecodeText } from "@/src/components/bits/DecodeText";
|
||||
import { Block } from "@/src/components/home/Block";
|
||||
import { ClosingCta } from "@/src/components/home/ClosingCta";
|
||||
@@ -109,9 +110,11 @@ export default function LandingPage() {
|
||||
</p>
|
||||
</PortraitSection>
|
||||
|
||||
<section className="bg-white">
|
||||
<div className="mx-auto w-full max-w-[1400px] px-5 py-16 md:px-12 md:py-28">
|
||||
<p className={`${MONO} m-0 mb-10`}>The path so far</p>
|
||||
<section className="relative overflow-clip bg-slate-900">
|
||||
<div className="mx-auto w-full max-w-[1400px] px-5 pb-20 pt-4 md:px-12 md:pb-36 md:pt-0">
|
||||
<p className="m-0 mb-10 font-mono text-[11px] uppercase tracking-[0.2em] text-slate-400">
|
||||
The path so far
|
||||
</p>
|
||||
<CareerLine stations={CAREER} />
|
||||
</div>
|
||||
</section>
|
||||
@@ -257,9 +260,11 @@ export default function LandingPage() {
|
||||
}
|
||||
>
|
||||
<Reveal>
|
||||
<div className="mb-16 overflow-hidden rounded-3xl bg-slate-900 p-4 md:mb-24 md:p-10">
|
||||
<ContactDiagram />
|
||||
</div>
|
||||
<BitAura source="bottom" tone="light" className="mb-16 md:mb-24">
|
||||
<div className="overflow-hidden rounded-3xl bg-slate-900 p-4 md:p-10">
|
||||
<ContactDiagram />
|
||||
</div>
|
||||
</BitAura>
|
||||
</Reveal>
|
||||
<div className="grid grid-cols-1 gap-x-12 gap-y-12 md:grid-cols-2 md:gap-y-16">
|
||||
{PRINCIPLES.map((item, i) => (
|
||||
@@ -278,17 +283,17 @@ export default function LandingPage() {
|
||||
<Block
|
||||
index="06"
|
||||
label="My work"
|
||||
tone="dark"
|
||||
title={
|
||||
<>
|
||||
Two sites I built. <span className={MUTED}>Both are live.</span>
|
||||
Two sites I built.{" "}
|
||||
<span className="text-slate-500">Both are live.</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<WorkShowcase items={WORK} />
|
||||
<Reveal>
|
||||
<p
|
||||
className={`${BODY} mt-20 max-w-3xl border-t border-slate-200 pt-8 md:mt-28`}
|
||||
>
|
||||
<p className="m-0 mt-20 max-w-3xl border-t border-slate-800 pt-8 font-sans text-lg leading-relaxed text-slate-300 md:mt-28 md:text-xl">
|
||||
Besides client work I build my own software, among other things a
|
||||
system in Rust and Solidity that runs on seven blockchains.
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user