From 44abc362bc1289578509cacd6f74a89500ce592e Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 14 Jul 2026 10:34:24 +0300 Subject: [PATCH] refactor(theme): muted offset shadows over hard brutalist stamp --- src/shared/styles/theme.css | 72 +++++++++++++++++++----- src/shared/ui/Button/ui/Button.tsx | 14 ++--- src/shared/ui/TechStack/ui/TechStack.tsx | 2 +- 3 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/shared/styles/theme.css b/src/shared/styles/theme.css index 5d280ce..54e87e7 100644 --- a/src/shared/styles/theme.css +++ b/src/shared/styles/theme.css @@ -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 diff --git a/src/shared/ui/Button/ui/Button.tsx b/src/shared/ui/Button/ui/Button.tsx index c3a8103..d5a71ee 100644 --- a/src/shared/ui/Button/ui/Button.tsx +++ b/src/shared/ui/Button/ui/Button.tsx @@ -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; const SIZES = { @@ -57,9 +55,9 @@ const SIZES = { lg: 'px-8 py-4 text-lg', } as const satisfies Record; -/* 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. diff --git a/src/shared/ui/TechStack/ui/TechStack.tsx b/src/shared/ui/TechStack/ui/TechStack.tsx index ea5de63..1dcd0e4 100644 --- a/src/shared/ui/TechStack/ui/TechStack.tsx +++ b/src/shared/ui/TechStack/ui/TechStack.tsx @@ -19,7 +19,7 @@ export function TechStackBrick({ name, className }: TechStackBrickProps) {