mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-17 05:32:05 +02:00
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:
@@ -1,5 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||||
<defs><filter id="sg-mglow" x="-60%" y="-60%" width="220%" height="220%"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter><clipPath id="sg-clip"><rect width="200" height="200" rx="26"/></clipPath><radialGradient id="sg-shock-s"><stop offset="0" stop-color="#35925c" stop-opacity="0.6"/><stop offset="1" stop-color="#35925c" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-u"><stop offset="0" stop-color="#e8689e" stop-opacity="0.6"/><stop offset="1" stop-color="#e8689e" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-c"><stop offset="0" stop-color="#4aade0" stop-opacity="0.6"/><stop offset="1" stop-color="#4aade0" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-h"><stop offset="0" stop-color="#3ad4c6" stop-opacity="0.6"/><stop offset="1" stop-color="#3ad4c6" stop-opacity="0"/></radialGradient></defs>
|
<defs><filter id="sg-mglow" x="-60%" y="-60%" width="220%" height="220%"><feGaussianBlur stdDeviation="2.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter><clipPath id="sg-clip"><rect width="200" height="200" rx="26"/></clipPath><radialGradient id="sg-shock-s"><stop offset="0" stop-color="#35925c" stop-opacity="0.6"/><stop offset="1" stop-color="#35925c" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-u"><stop offset="0" stop-color="#e8689e" stop-opacity="0.6"/><stop offset="1" stop-color="#e8689e" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-c"><stop offset="0" stop-color="#4aade0" stop-opacity="0.6"/><stop offset="1" stop-color="#4aade0" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-h"><stop offset="0" stop-color="#3ad4c6" stop-opacity="0.6"/><stop offset="1" stop-color="#3ad4c6" stop-opacity="0"/></radialGradient></defs>
|
||||||
<style>
|
<style>
|
||||||
.rn { animation: sg-rain linear infinite; }
|
.rn { animation: sg-rain linear infinite; }
|
||||||
@keyframes sg-rain { from { transform: translateY(-100px); } to { transform: translateY(320px); } }
|
@keyframes sg-rain { from { transform: translateY(-100px); } to { transform: translateY(320px); } }
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
@keyframes sg-slam { 0%, 2% { transform: scale(0); } 5% { transform: scale(1.15); } 8% { transform: scale(1); } 85% { transform: scale(1); } 95% { transform: scale(1); opacity: 1; } 100% { transform: scale(0); opacity: 0; } }
|
@keyframes sg-slam { 0%, 2% { transform: scale(0); } 5% { transform: scale(1.15); } 8% { transform: scale(1); } 85% { transform: scale(1); } 95% { transform: scale(1); opacity: 1; } 100% { transform: scale(0); opacity: 0; } }
|
||||||
.orb { transform-box: fill-box; transform-origin: center; animation: sg-orb 1.5s ease-in-out infinite; }
|
.orb { transform-box: fill-box; transform-origin: center; animation: sg-orb 1.5s ease-in-out infinite; }
|
||||||
@keyframes sg-orb { 0%,100% { transform: scale(1); opacity: 0.6; } 50% { transform: scale(2); opacity: 1; } }
|
@keyframes sg-orb { 0%,100% { transform: scale(1); opacity: 0.6; } 50% { transform: scale(2); opacity: 1; } }
|
||||||
.bdr { animation: sg-hue 4s linear infinite, sg-breathe-o 2s ease-in-out infinite; }
|
.bdr { animation: sg-bcycle 4s linear infinite, sg-breathe-o 2s ease-in-out infinite; }
|
||||||
@keyframes sg-hue { from { filter: hue-rotate(0deg); } to { filter: hue-rotate(360deg); } }
|
@keyframes sg-bcycle { 0%,100% { stroke: #7ddfbe; } 25% { stroke: #e8689e; } 50% { stroke: #4aade0; } 75% { stroke: #35925c; } }
|
||||||
@keyframes sg-breathe-o { 0%,100% { opacity: 0.7; } 50% { opacity: 1; } }
|
@keyframes sg-breathe-o { 0%,100% { opacity: 0.7; } 50% { opacity: 1; } }
|
||||||
.slam-s { animation-delay: 0s; }
|
.slam-s { animation-delay: 0s; }
|
||||||
.shock-s { animation-delay: 0s; }
|
.shock-s { animation-delay: 0s; }
|
||||||
@@ -66,8 +66,8 @@
|
|||||||
<path d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#45c99e" stroke-width="2" opacity="0.25" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
<path d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#45c99e" stroke-width="2" opacity="0.25" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
<path class="zap" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#7ddfbe" stroke-width="3" opacity="0.8" stroke-linecap="round" stroke-linejoin="round" fill="none" filter="url(#sg-mglow)" stroke-dasharray="15 588.9"/>
|
<path class="zap" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#7ddfbe" stroke-width="3" opacity="0.8" stroke-linecap="round" stroke-linejoin="round" fill="none" filter="url(#sg-mglow)" stroke-dasharray="15 588.9"/>
|
||||||
<path class="ctr" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#ffffff" stroke-width="1.5" opacity="0.5" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-dasharray="5 598.9"/>
|
<path class="ctr" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#ffffff" stroke-width="1.5" opacity="0.5" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-dasharray="5 598.9"/>
|
||||||
<path class="crk" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#45c99e" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none" filter="url(#sg-mglow)" stroke-dasharray="3 40 8 60 2 80 5 400"/>
|
<path class="crk" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#45c99e" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-dasharray="3 40 8 60 2 80 5 400"/>
|
||||||
<g filter="url(#sg-mglow)">
|
<g>
|
||||||
<g transform="translate(20,28)"><g class="orb" style="animation-delay:0s"><circle r="2.5" fill="#45c99e"/></g></g>
|
<g transform="translate(20,28)"><g class="orb" style="animation-delay:0s"><circle r="2.5" fill="#45c99e"/></g></g>
|
||||||
<g transform="translate(104,100)"><g class="orb" style="animation-delay:0.5s"><circle r="2.5" fill="#45c99e"/></g></g>
|
<g transform="translate(104,100)"><g class="orb" style="animation-delay:0.5s"><circle r="2.5" fill="#45c99e"/></g></g>
|
||||||
<g transform="translate(172,172)"><g class="orb" style="animation-delay:1s"><circle r="2.5" fill="#45c99e"/></g></g>
|
<g transform="translate(172,172)"><g class="orb" style="animation-delay:1s"><circle r="2.5" fill="#45c99e"/></g></g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||||
<defs><filter id="sg-mglow" x="-60%" y="-60%" width="220%" height="220%"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter><clipPath id="sg-clip"><rect width="200" height="200" rx="26"/></clipPath><radialGradient id="sg-shock-s"><stop offset="0" stop-color="#1a5c38" stop-opacity="0.6"/><stop offset="1" stop-color="#1a5c38" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-u"><stop offset="0" stop-color="#b83670" stop-opacity="0.6"/><stop offset="1" stop-color="#b83670" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-c"><stop offset="0" stop-color="#176a92" stop-opacity="0.6"/><stop offset="1" stop-color="#176a92" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-h"><stop offset="0" stop-color="#12857e" stop-opacity="0.6"/><stop offset="1" stop-color="#12857e" stop-opacity="0"/></radialGradient></defs>
|
<defs><filter id="sg-mglow" x="-60%" y="-60%" width="220%" height="220%"><feGaussianBlur stdDeviation="2.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter><clipPath id="sg-clip"><rect width="200" height="200" rx="26"/></clipPath><radialGradient id="sg-shock-s"><stop offset="0" stop-color="#1a5c38" stop-opacity="0.6"/><stop offset="1" stop-color="#1a5c38" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-u"><stop offset="0" stop-color="#b83670" stop-opacity="0.6"/><stop offset="1" stop-color="#b83670" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-c"><stop offset="0" stop-color="#176a92" stop-opacity="0.6"/><stop offset="1" stop-color="#176a92" stop-opacity="0"/></radialGradient><radialGradient id="sg-shock-h"><stop offset="0" stop-color="#12857e" stop-opacity="0.6"/><stop offset="1" stop-color="#12857e" stop-opacity="0"/></radialGradient></defs>
|
||||||
<style>
|
<style>
|
||||||
.rn { animation: sg-rain linear infinite; }
|
.rn { animation: sg-rain linear infinite; }
|
||||||
@keyframes sg-rain { from { transform: translateY(-100px); } to { transform: translateY(320px); } }
|
@keyframes sg-rain { from { transform: translateY(-100px); } to { transform: translateY(320px); } }
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
@keyframes sg-slam { 0%, 2% { transform: scale(0); } 5% { transform: scale(1.15); } 8% { transform: scale(1); } 85% { transform: scale(1); } 95% { transform: scale(1); opacity: 1; } 100% { transform: scale(0); opacity: 0; } }
|
@keyframes sg-slam { 0%, 2% { transform: scale(0); } 5% { transform: scale(1.15); } 8% { transform: scale(1); } 85% { transform: scale(1); } 95% { transform: scale(1); opacity: 1; } 100% { transform: scale(0); opacity: 0; } }
|
||||||
.orb { transform-box: fill-box; transform-origin: center; animation: sg-orb 1.5s ease-in-out infinite; }
|
.orb { transform-box: fill-box; transform-origin: center; animation: sg-orb 1.5s ease-in-out infinite; }
|
||||||
@keyframes sg-orb { 0%,100% { transform: scale(1); opacity: 0.6; } 50% { transform: scale(2); opacity: 1; } }
|
@keyframes sg-orb { 0%,100% { transform: scale(1); opacity: 0.6; } 50% { transform: scale(2); opacity: 1; } }
|
||||||
.bdr { animation: sg-hue 4s linear infinite, sg-breathe-o 2s ease-in-out infinite; }
|
.bdr { animation: sg-bcycle 4s linear infinite, sg-breathe-o 2s ease-in-out infinite; }
|
||||||
@keyframes sg-hue { from { filter: hue-rotate(0deg); } to { filter: hue-rotate(360deg); } }
|
@keyframes sg-bcycle { 0%,100% { stroke: #2f8f5e; } 25% { stroke: #b83670; } 50% { stroke: #176a92; } 75% { stroke: #1a5c38; } }
|
||||||
@keyframes sg-breathe-o { 0%,100% { opacity: 0.7; } 50% { opacity: 1; } }
|
@keyframes sg-breathe-o { 0%,100% { opacity: 0.7; } 50% { opacity: 1; } }
|
||||||
.slam-s { animation-delay: 0s; }
|
.slam-s { animation-delay: 0s; }
|
||||||
.shock-s { animation-delay: 0s; }
|
.shock-s { animation-delay: 0s; }
|
||||||
@@ -66,8 +66,8 @@
|
|||||||
<path d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#1a5c38" stroke-width="2" opacity="0.25" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
<path d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#1a5c38" stroke-width="2" opacity="0.25" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
<path class="zap" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#2f8f5e" stroke-width="3" opacity="0.8" stroke-linecap="round" stroke-linejoin="round" fill="none" filter="url(#sg-mglow)" stroke-dasharray="15 588.9"/>
|
<path class="zap" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#2f8f5e" stroke-width="3" opacity="0.8" stroke-linecap="round" stroke-linejoin="round" fill="none" filter="url(#sg-mglow)" stroke-dasharray="15 588.9"/>
|
||||||
<path class="ctr" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#0a0a14" stroke-width="1.5" opacity="0.5" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-dasharray="5 598.9"/>
|
<path class="ctr" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#0a0a14" stroke-width="1.5" opacity="0.5" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-dasharray="5 598.9"/>
|
||||||
<path class="crk" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#1a5c38" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none" filter="url(#sg-mglow)" stroke-dasharray="3 40 8 60 2 80 5 400"/>
|
<path class="crk" d="m 20,28 h 66 l 6,6 h 16 l 6,-6 h 58 l 6,6 v 60 l -6,6 H 28 l -6,6 v 60 l 6,6 h 58 l 6,-6 h 16 l 6,6 h 58" stroke="#1a5c38" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-dasharray="3 40 8 60 2 80 5 400"/>
|
||||||
<g filter="url(#sg-mglow)">
|
<g>
|
||||||
<g transform="translate(20,28)"><g class="orb" style="animation-delay:0s"><circle r="2.5" fill="#1a5c38"/></g></g>
|
<g transform="translate(20,28)"><g class="orb" style="animation-delay:0s"><circle r="2.5" fill="#1a5c38"/></g></g>
|
||||||
<g transform="translate(104,100)"><g class="orb" style="animation-delay:0.5s"><circle r="2.5" fill="#1a5c38"/></g></g>
|
<g transform="translate(104,100)"><g class="orb" style="animation-delay:0.5s"><circle r="2.5" fill="#1a5c38"/></g></g>
|
||||||
<g transform="translate(172,172)"><g class="orb" style="animation-delay:1s"><circle r="2.5" fill="#1a5c38"/></g></g>
|
<g transform="translate(172,172)"><g class="orb" style="animation-delay:1s"><circle r="2.5" fill="#1a5c38"/></g></g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Such Software",
|
"name": "Such Software",
|
||||||
"short_name": "Such Software",
|
"short_name": "Such Software",
|
||||||
"description": "Next-generation web applications engineered for scale, compliance, and performance.",
|
"description": "A Kennett Square, PA software studio building custom online stores, web apps, mobile games, and Monero & crypto tools.",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#0a0a0a",
|
"background_color": "#0a0a0a",
|
||||||
|
|||||||
@@ -286,14 +286,29 @@
|
|||||||
/* Hero title: an animated gradient sheen. LCP-safe — the text paints immediately with the
|
/* Hero title: an animated gradient sheen. LCP-safe — the text paints immediately with the
|
||||||
gradient; only the gradient POSITION animates, which never delays the largest paint. The
|
gradient; only the gradient POSITION animates, which never delays the largest paint. The
|
||||||
global reduced-motion rule freezes it to the base gradient. */
|
global reduced-motion rule freezes it to the base gradient. */
|
||||||
|
/* The brand's four-colour system (S green, U magenta, C blue, H teal), one per glyph.
|
||||||
|
Light theme uses the light palette; .dark swaps in the dark palette, which is the
|
||||||
|
same hues lifted for contrast on near-black -- from packs/such-software/pack.json. */
|
||||||
.hero-title {
|
.hero-title {
|
||||||
background-image: linear-gradient(110deg, #22d3ee 0%, #3b82f6 30%, #7dd3fc 50%, #3b82f6 70%, #22d3ee 100%);
|
background-image: linear-gradient(110deg, #1a5c38 0%, #b83670 30%, #176a92 55%, #12857e 80%, #1a5c38 100%);
|
||||||
background-size: 220% auto;
|
background-size: 220% auto;
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
animation: hero-sheen 9s ease-in-out infinite;
|
animation: hero-sheen 9s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
.dark .hero-title {
|
||||||
|
background-image: linear-gradient(110deg, #35925c 0%, #e8689e 30%, #4aade0 55%, #3ad4c6 80%, #35925c 100%);
|
||||||
|
}
|
||||||
|
.brand-gradient-text {
|
||||||
|
background-image: linear-gradient(90deg, #1a5c38, #b83670, #176a92, #12857e);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
.dark .brand-gradient-text {
|
||||||
|
background-image: linear-gradient(90deg, #35925c, #e8689e, #4aade0, #3ad4c6);
|
||||||
|
}
|
||||||
@keyframes hero-sheen {
|
@keyframes hero-sheen {
|
||||||
0%, 100% { background-position: 0% center; }
|
0%, 100% { background-position: 0% center; }
|
||||||
50% { background-position: 100% center; }
|
50% { background-position: 100% center; }
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const structuredData = {
|
|||||||
name: "Such Software LLC",
|
name: "Such Software LLC",
|
||||||
url: "https://such.software",
|
url: "https://such.software",
|
||||||
logo: "https://such.software/images/branding/icon-512.png",
|
logo: "https://such.software/images/branding/icon-512.png",
|
||||||
description: "E-commerce infrastructure, custom app development, and accessible web applications. Engineered for scale, compliance, and performance.",
|
description: "A Kennett Square, PA software studio building custom online stores, web apps, mobile games, and Monero & crypto tools.",
|
||||||
address: {
|
address: {
|
||||||
"@type": "PostalAddress",
|
"@type": "PostalAddress",
|
||||||
streetAddress: "110 E State St, Suite 300",
|
streetAddress: "110 E State St, Suite 300",
|
||||||
|
|||||||
@@ -21,23 +21,23 @@ export default function Home() {
|
|||||||
{/* Decorative aura behind the title (aria-hidden, LCP-safe, motion-safe). */}
|
{/* Decorative aura behind the title (aria-hidden, LCP-safe, motion-safe). */}
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="hero-aura pointer-events-none absolute left-1/2 top-24 sm:top-16 -z-10 h-64 w-[46rem] max-w-[92vw] rounded-full bg-cyan-400/25 blur-3xl"
|
className="hero-aura pointer-events-none absolute left-1/2 top-24 sm:top-16 -z-10 h-64 w-[46rem] max-w-[92vw] rounded-full bg-[#12857e]/25 blur-3xl"
|
||||||
/>
|
/>
|
||||||
<h1
|
<h1
|
||||||
id="hero-title"
|
id="hero-title"
|
||||||
className="hero-title text-4xl sm:text-5xl md:text-7xl font-bold mb-6 pb-2"
|
className="hero-title text-4xl sm:text-5xl md:text-7xl font-bold mb-6 pb-2"
|
||||||
style={{ maxWidth: "min(calc(90vw - 120px), 100%)" }}
|
style={{ maxWidth: "min(calc(90vw - 120px), 100%)" }}
|
||||||
>
|
>
|
||||||
<span className="block">Precision Engineering</span>
|
<span className="block">Custom E-commerce,</span>
|
||||||
<span className="block">for Everyone</span>
|
<span className="block">Apps & Crypto Tools</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="max-w-2xl text-xl text-muted-foreground mb-8">
|
<p className="max-w-2xl text-xl text-muted-foreground mb-8">
|
||||||
Custom e-commerce, websites, apps, and consulting. Plus our own mobile apps and games. A software studio in Kennett Square, PA.
|
Websites, games, payments, and consulting too — plus our own mobile apps. A software studio in Kennett Square, PA. Precision engineering for everyone.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center gap-4 mb-14">
|
<div className="flex flex-wrap justify-center gap-4 mb-14">
|
||||||
<Link href="/products">
|
<Link href="/products">
|
||||||
<Button size="lg" className="rounded-full shadow-lg shadow-cyan-500/25 hover:shadow-cyan-400/40 hover:scale-[1.03] transition-all">View Portfolio</Button>
|
<Button size="lg" className="rounded-full shadow-lg shadow-[#12857e]/25 hover:shadow-[#3ad4c6]/40 hover:scale-[1.03] transition-all">View Portfolio</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="#contact">
|
<Link href="#contact">
|
||||||
<Button variant="outline" size="lg" className="rounded-full hover:scale-[1.03] transition-all">Start a Project</Button>
|
<Button variant="outline" size="lg" className="rounded-full hover:scale-[1.03] transition-all">Start a Project</Button>
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { ProductsGrid, type Product } from "@/components/portfolio/products-grid
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Products: Crypto Tools, Games & Commerce | Such Software",
|
title: "Portfolio: Products & Projects | Such Software",
|
||||||
description: "Explore Such Software's products: Wownerogue Monero roguelike, Neroswap crypto aggregator, Smirk wallet, headless Webshops, our mobile games, and nuclear games in development.",
|
description: "Explore Such Software's products: Wownerogue Monero roguelike, Neroswap crypto aggregator, Smirk wallet, headless Webshops, our mobile games, and nuclear games in development.",
|
||||||
alternates: { canonical: "/products" },
|
alternates: { canonical: "/products" },
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Products: Crypto Tools, Games & Commerce | Such Software",
|
title: "Portfolio: Products & Projects | Such Software",
|
||||||
description: "Explore Such Software's products: Wownerogue Monero roguelike, Neroswap crypto aggregator, Smirk wallet, headless Webshops, our mobile games, and nuclear games in development.",
|
description: "Explore Such Software's products: Wownerogue Monero roguelike, Neroswap crypto aggregator, Smirk wallet, headless Webshops, our mobile games, and nuclear games in development.",
|
||||||
type: "website",
|
type: "website",
|
||||||
},
|
},
|
||||||
@@ -146,13 +146,13 @@ export default function ProductsPage() {
|
|||||||
return (
|
return (
|
||||||
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
|
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
|
||||||
<Header />
|
<Header />
|
||||||
<div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4 pb-24 md:pb-20">
|
<div id="main-content" className="z-10 w-full max-w-6xl mx-auto py-20 px-4 pb-24 md:pb-20">
|
||||||
<div className="text-center mb-16">
|
<div className="text-center mb-16">
|
||||||
<h1 className="text-4xl md:text-6xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-amber-400 to-purple-500">
|
<h1 className="text-4xl md:text-6xl font-bold mb-6 brand-gradient-text">
|
||||||
Portfolio
|
Portfolio
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
||||||
The tools and apps we have shipped, from commerce backends to consumer games, plus three nuclear-physics games in development.
|
Products & projects — from commerce backends to consumer games, plus three nuclear-physics games in development.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function ServicesPage() {
|
|||||||
<div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4">
|
<div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4">
|
||||||
{/* Page header */}
|
{/* Page header */}
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h1 className="text-4xl md:text-6xl font-bold mb-6 pb-3 bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 to-blue-500">
|
<h1 className="text-4xl md:text-6xl font-bold mb-6 pb-3 brand-gradient-text">
|
||||||
What we build
|
What we build
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
||||||
|
|||||||
@@ -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 */}
|
{/* Cover text hidden on mobile (logo + corner icons carry it there); fades out on enter */}
|
||||||
<p
|
<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" }}
|
style={{ animationDuration: "700ms", animationDelay: "120ms", animationFillMode: "backwards" }}
|
||||||
>
|
>
|
||||||
Precision Engineering for Everyone
|
Precision Engineering for Everyone
|
||||||
|
|||||||
@@ -49,16 +49,18 @@ export function HomeShell({ children }: { children: ReactNode }) {
|
|||||||
if (entered) return;
|
if (entered) return;
|
||||||
const el = splashRef.current;
|
const el = splashRef.current;
|
||||||
if (!el) return;
|
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(
|
const io = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
if (!entry.isIntersecting) {
|
if (entry.intersectionRatio < 0.6) {
|
||||||
restoreScroll.current = Math.max(0, window.scrollY - el.offsetHeight);
|
restoreScroll.current = Math.max(0, window.scrollY - el.offsetHeight);
|
||||||
sessionStorage.setItem("such:entered", "1");
|
sessionStorage.setItem("such:entered", "1");
|
||||||
setEntered(true);
|
setEntered(true);
|
||||||
setSplashUp(false); // scrolled past: just unmount, no overlay fade needed
|
setSplashUp(false); // scrolled past: just unmount, no overlay fade needed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ threshold: 0 },
|
{ threshold: [0, 0.6] },
|
||||||
);
|
);
|
||||||
io.observe(el);
|
io.observe(el);
|
||||||
return () => io.disconnect();
|
return () => io.disconnect();
|
||||||
|
|||||||
@@ -133,16 +133,16 @@ export function ProductsGrid({ products }: { products: Product[] }) {
|
|||||||
id="product-sort"
|
id="product-sort"
|
||||||
value={sort}
|
value={sort}
|
||||||
onChange={(e) => setSort(e.target.value as SortKey)}
|
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="release">Release date (newest)</option>
|
||||||
<option value="name">Name (A–Z)</option>
|
<option value="name">Name (A–Z)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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) => (
|
{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">
|
<Link href={`/products/${product.slug}`} className="block h-full">
|
||||||
<div className="h-full cursor-pointer">
|
<div className="h-full cursor-pointer">
|
||||||
<ProductCardInner product={product} />
|
<ProductCardInner product={product} />
|
||||||
|
|||||||
Reference in New Issue
Block a user