Feature/brutalism overhaul #10

Merged
ilia merged 9 commits from feature/brutalism-overhaul into main 2026-07-14 07:40:18 +00:00
3 changed files with 66 additions and 22 deletions
Showing only changes of commit 44abc362bc - Show all commits
+59 -13
View File
@@ -69,17 +69,21 @@
--space-20: 10rem;
/* === BORDERS === */
--border-width: 3px;
--radius: 0px;
--border-width: 1.5px;
--radius: 5px;
/* === BRUTALIST SHADOWS === */
--shadow-brutal-xs: 1px 1px 0 var(--blue);
--shadow-brutal-sm: 3px 3px 0 var(--blue);
--shadow-brutal: 5px 5px 0 var(--blue);
--shadow-brutal-md: 7px 7px 0 var(--blue);
--shadow-brutal-lg: 8px 8px 0 var(--blue);
--shadow-brutal-xl: 10px 10px 0 var(--blue);
--shadow-brutal-2xl: 12px 12px 0 var(--blue);
/* Muted offset shadow — quieter than the old hard-blue brutalist stamp.
* Softer alpha + tighter offsets keep the offset-shadow feel, just lighter. */
--shadow-color: rgba(4, 28, 243, 0.25);
/* === OFFSET SHADOWS === */
--shadow-brutal-xs: 1px 1px 0 var(--shadow-color);
--shadow-brutal-sm: 2px 2px 0 var(--shadow-color);
--shadow-brutal: 3px 3px 0 var(--shadow-color);
--shadow-brutal-md: 3px 3px 0 var(--shadow-color);
--shadow-brutal-lg: 4px 4px 0 var(--shadow-color);
--shadow-brutal-xl: 5px 5px 0 var(--shadow-color);
--shadow-brutal-2xl: 6px 6px 0 var(--shadow-color);
/* === GRID === */
--grid-gap: var(--space-3);
@@ -237,9 +241,46 @@
}
}
/* Button elevation transition — only transform animates; shadow snaps instantly */
.btn-transition {
transition: transform 0.13s var(--ease-micro);
/* Button elevation transition — transform + shadow animate together over 130ms
* on the micro easing curve; the spring feel comes from the curve, no keyframes. */
@utility btn-transition {
transition: transform 130ms var(--ease-micro);
}
/* Offset "shadow" as a static pseudo-block instead of box-shadow. box-shadow
* swims because it repaints on the main thread while the button's translate
* runs on the compositor — the two desync per frame. Here both the button and
* its ::before are transforms, so they stay frame-synced. The ::before counter-
* translates to hold a fixed 3px world offset while the button lifts/presses,
* so the shadow appears frozen and only the button moves. */
.btn-shadow {
position: relative;
z-index: 0;
transition: transform 130ms var(--ease-micro);
}
.btn-shadow::before {
content: "";
position: absolute;
/* ponytail: inset:0 tracks the padding box, ~1.5px shy of the border box;
* imperceptible on a hard offset block. Widen with negative inset if it shows. */
inset: 0;
z-index: -1;
border-radius: inherit;
background: var(--shadow-color);
transform: translate(3px, 3px);
transition: transform 130ms var(--ease-micro);
}
.btn-shadow:hover {
transform: translate(-2px, -2px);
}
.btn-shadow:hover::before {
transform: translate(5px, 5px);
}
.btn-shadow:active {
transform: translate(2px, 2px);
}
.btn-shadow:active::before {
transform: translate(1px, 1px);
}
/* Brutalist utility classes */
@@ -254,6 +295,7 @@
}
@utility brutal-border {
border: var(--border-width) solid var(--blue);
border-radius: var(--radius);
}
@utility brutal-border-top {
border-top: var(--border-width) solid var(--blue);
@@ -272,6 +314,7 @@
* ancestor must not have overflow:hidden or the outline gets clipped. */
@utility brutal-outline {
outline: var(--border-width) solid var(--blue);
border-radius: var(--radius);
}
/* Tiled blue dot pattern — applied to body::before (page-wide) and reusable
* on any surface that should share the same paper-grain texture. The SVG
@@ -479,6 +522,9 @@ dialog.lightbox {
box-sizing: content-box;
max-width: calc(100vw - 2rem);
max-height: calc(100vh - 10rem);
/* Match the wrapper's brutal-outline radius so image corners sit flush
* inside the rounded outline instead of poking past it. */
border-radius: var(--radius);
}
/* Disable transition animation for Firefox
+6 -8
View File
@@ -41,14 +41,12 @@ function isAnchorProps(props: RestButton | RestAnchor): props is RestAnchor {
}
const VARIANTS = {
primary:
'brutal-border bg-blue text-cream shadow-[5px_5px_0_rgba(4,28,243,0.35)] hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-[7px_7px_0_rgba(4,28,243,0.35)] active:translate-x-0.5 active:translate-y-0.5 active:shadow-[1px_1px_0_rgba(4,28,243,0.35)]',
secondary:
'brutal-border bg-blue text-cream shadow-brutal hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-brutal-md active:translate-x-0.5 active:translate-y-0.5 active:shadow-brutal-xs',
primary: 'brutal-border bg-blue text-cream btn-shadow',
secondary: 'brutal-border bg-blue text-cream btn-shadow',
outline:
'brutal-border border-blue/35 bg-cream text-blue hover:border-blue hover:bg-blue/10 active:bg-blue active:text-cream',
ghost:
'brutal-border bg-transparent text-blue hover:-translate-x-0.5 hover:-translate-y-0.5 active:translate-x-0.5 active:translate-y-0.5',
'brutal-border bg-transparent text-blue btn-transition hover:-translate-x-0.5 hover:-translate-y-0.5 active:translate-x-0.5 active:translate-y-0.5',
} as const satisfies Record<ButtonVariant, string>;
const SIZES = {
@@ -57,9 +55,9 @@ const SIZES = {
lg: 'px-8 py-4 text-lg',
} as const satisfies Record<ButtonSize, string>;
/* box-shadow excluded from transition intentionally — snaps instantly so the
* eye follows the 130ms button movement, not the shadow change. */
const BASE = 'cursor-pointer btn-transition uppercase tracking-wider';
/* Elevation lives per-variant: primary/secondary use btn-shadow (static offset
* block, button moves), ghost uses btn-transition + translate. */
const BASE = 'cursor-pointer uppercase tracking-wider';
/**
* Brutalist button with variants and sizes.
+1 -1
View File
@@ -19,7 +19,7 @@ export function TechStackBrick({ name, className }: TechStackBrickProps) {
<div
className={cn(
'brutal-border brutal-shadow bg-cream px-4 py-3 text-center',
'transition-all duration-200 hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px]',
'transition-all duration-200 hover:shadow-none hover:translate-x-[3px] hover:translate-y-[3px]',
className,
)}
>