feat(site): one voice, brand gradients, a lighter splash gate, denser portfolio

- Voice unified on the title-tag register: the homepage h1 now says what we
  sell (Custom E-commerce, Apps & Crypto Tools); 'Precision engineering for
  everyone' is demoted to the supporting line. The structured-data and
  manifest descriptions now match the OG description instead of carrying
  their own registers.
- The off-palette cyan/blue gradients are gone: .hero-title and the new
  .brand-gradient-text use the four-colour system (light palette on light,
  dark palette on dark, per the pack). Splash cover text, services and
  portfolio headings, the hero aura, and button shadows all follow.
- The splash now dismisses once 40% scrolled past instead of requiring the
  whole screen to leave the viewport (it was already once-per-session and
  reduced-motion-skipping).
- Portfolio: heading 'Portfolio', subtitle 'Products & projects', meta title
  to match; grid grows to three columns on xl in a max-w-6xl container.
- anim3_matrix slimmed: single-layer glow, unfiltered crackle and orbs, and
  the border cycles stroke colour instead of animating filter:hue-rotate --
  the filter animation forced a full filter re-render per frame and pegged a
  core on the page hosting it.
This commit is contained in:
jwinterm
2026-08-16 00:04:28 -04:00
parent c73b9f1c04
commit 261cb04667
11 changed files with 47 additions and 30 deletions
@@ -196,7 +196,7 @@ export function HeroSplash({ onEnter, sectionRef, leaving }: { onEnter: () => vo
{/* Cover text hidden on mobile (logo + corner icons carry it there); fades out on enter */}
<p
className={`mt-10 hidden sm:block bg-gradient-to-r from-cyan-400 to-blue-600 bg-clip-text pb-1 text-3xl font-bold text-transparent sm:text-4xl md:text-5xl transition-opacity duration-700 motion-safe:animate-in motion-safe:fade-in ${entering ? "opacity-0" : "opacity-100"}`}
className={`mt-10 hidden sm:block brand-gradient-text pb-1 text-3xl font-bold text-transparent sm:text-4xl md:text-5xl transition-opacity duration-700 motion-safe:animate-in motion-safe:fade-in ${entering ? "opacity-0" : "opacity-100"}`}
style={{ animationDuration: "700ms", animationDelay: "120ms", animationFillMode: "backwards" }}
>
Precision Engineering for Everyone
@@ -49,16 +49,18 @@ export function HomeShell({ children }: { children: ReactNode }) {
if (entered) return;
const el = splashRef.current;
if (!el) return;
// Enter once 40% of the splash has scrolled by: requiring the whole screen to
// leave the viewport made the reveal feel like a toll booth.
const io = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) {
if (entry.intersectionRatio < 0.6) {
restoreScroll.current = Math.max(0, window.scrollY - el.offsetHeight);
sessionStorage.setItem("such:entered", "1");
setEntered(true);
setSplashUp(false); // scrolled past: just unmount, no overlay fade needed
}
},
{ threshold: 0 },
{ threshold: [0, 0.6] },
);
io.observe(el);
return () => io.disconnect();
@@ -133,16 +133,16 @@ export function ProductsGrid({ products }: { products: Product[] }) {
id="product-sort"
value={sort}
onChange={(e) => setSort(e.target.value as SortKey)}
className="rounded-lg border border-border bg-background px-3 py-1.5 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-cyan-400/50"
className="rounded-lg border border-border bg-background px-3 py-1.5 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-[#3ad4c6]/50"
>
<option value="release">Release date (newest)</option>
<option value="name">Name (A–Z)</option>
</select>
</div>
<div className="grid md:grid-cols-2 gap-8">
<div className="grid md:grid-cols-2 xl:grid-cols-3 gap-8">
{sorted.map((product, i) => (
<ScrollReveal key={product.slug} delay={(i % 2) * 0.08}>
<ScrollReveal key={product.slug} delay={(i % 3) * 0.08}>
<Link href={`/products/${product.slug}`} className="block h-full">
<div className="h-full cursor-pointer">
<ProductCardInner product={product} />