mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-17 05:32:05 +02:00
Upgrade Next.js to 15, fix first-load theme bug, add SVG assets
- Upgrade next 14.1.0 → 15.5.9, next-themes 0.2.1 → 0.4.6 - Fix theme not initializing on first load after server restart - Add SVG logos and favicon, update site-logo to use SVGs - Create shared Header component with skip-to-main link - Add portfolio links to bento grid cards - Improve accessibility: aria-describedby, prefers-reduced-motion - Fix useFormState → useActionState for React 19 compatibility - Fix founder-signature icon (use square favicon instead of wide logo)
This commit is contained in:
@@ -14,8 +14,8 @@
|
|||||||
"@repo/ui": "workspace:*",
|
"@repo/ui": "workspace:*",
|
||||||
"framer-motion": "^11.0.0",
|
"framer-motion": "^11.0.0",
|
||||||
"lucide-react": "^0.350.0",
|
"lucide-react": "^0.350.0",
|
||||||
"next": "14.1.0",
|
"next": "^15.1.3",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.1.0",
|
"eslint-config-next": "^15.1.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.3.0",
|
"tailwindcss": "^3.3.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="190.59711mm"
|
||||||
|
height="190.59711mm"
|
||||||
|
viewBox="0 0 190.59711 190.59711"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="favicon.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
inkscape:export-filename="favicon.png"
|
||||||
|
inkscape:export-xdpi="25.586958"
|
||||||
|
inkscape:export-ydpi="25.586958"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.91490602"
|
||||||
|
inkscape:cx="360.14628"
|
||||||
|
inkscape:cy="207.1251"
|
||||||
|
inkscape:window-width="2318"
|
||||||
|
inkscape:window-height="1365"
|
||||||
|
inkscape:window-x="4768"
|
||||||
|
inkscape:window-y="177"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-49.436114,-100.65314)"><g
|
||||||
|
id="g1403"
|
||||||
|
transform="translate(160.4222,89.628141)"><g
|
||||||
|
id="g464"
|
||||||
|
transform="translate(0,-102.13792)"><rect
|
||||||
|
style="fill:#3baf8a;fill-opacity:1;stroke-width:2"
|
||||||
|
id="rect2694"
|
||||||
|
width="10.037269"
|
||||||
|
height="101.95406"
|
||||||
|
x="-72.188728"
|
||||||
|
y="157.86139"
|
||||||
|
ry="1.4290794" /><rect
|
||||||
|
style="fill:#2190a4;fill-opacity:1;stroke-width:2"
|
||||||
|
id="rect2694-3"
|
||||||
|
width="10.037269"
|
||||||
|
height="101.95406"
|
||||||
|
x="-64.071434"
|
||||||
|
y="157.86139"
|
||||||
|
ry="1.4290794" /><rect
|
||||||
|
style="fill:#2190a4;fill-opacity:1;stroke-width:1.6256"
|
||||||
|
id="rect2694-3-2"
|
||||||
|
width="6.6310406"
|
||||||
|
height="101.95406"
|
||||||
|
x="-64.071434"
|
||||||
|
y="157.86139"
|
||||||
|
ry="0" /></g><g
|
||||||
|
id="g468"
|
||||||
|
transform="translate(0,-102.28801)"><rect
|
||||||
|
style="fill:#3baf8a;fill-opacity:1;stroke-width:2.02482"
|
||||||
|
id="rect2694-6"
|
||||||
|
width="17.863615"
|
||||||
|
height="58.716511"
|
||||||
|
x="-40.229259"
|
||||||
|
y="201.24904"
|
||||||
|
ry="0.8230232" /><rect
|
||||||
|
style="fill:#2190a4;fill-opacity:1;stroke-width:1.78573"
|
||||||
|
id="rect2694-6-7"
|
||||||
|
width="17.863615"
|
||||||
|
height="45.668602"
|
||||||
|
x="-40.30563"
|
||||||
|
y="157.67754"
|
||||||
|
ry="0.64013201"
|
||||||
|
inkscape:transform-center-x="3.412718"
|
||||||
|
inkscape:transform-center-y="23.898841" /></g><rect
|
||||||
|
style="fill:#286442;fill-opacity:1;stroke-width:2.67119"
|
||||||
|
id="rect2694-6-5"
|
||||||
|
width="17.863615"
|
||||||
|
height="102.18733"
|
||||||
|
x="-8.6201973"
|
||||||
|
y="55.490204"
|
||||||
|
ry="1.4323492" /><g
|
||||||
|
id="g472"
|
||||||
|
transform="translate(0,-102.85913)"><rect
|
||||||
|
style="fill:#ea62a0;fill-opacity:1;stroke-width:2.14284"
|
||||||
|
id="rect2694-6-3"
|
||||||
|
width="17.863615"
|
||||||
|
height="65.760574"
|
||||||
|
x="22.950037"
|
||||||
|
y="157.82869"
|
||||||
|
ry="0.92175913" /><rect
|
||||||
|
style="fill:#3ca5d4;fill-opacity:1;stroke-width:1.8415"
|
||||||
|
id="rect2694-6-56"
|
||||||
|
width="17.863615"
|
||||||
|
height="48.565922"
|
||||||
|
x="22.929722"
|
||||||
|
y="211.97075"
|
||||||
|
ry="0.68074346" /></g></g><circle
|
||||||
|
style="fill:none;stroke:#3ca5d4;stroke-width:3.40352;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path353"
|
||||||
|
cx="144.73466"
|
||||||
|
cy="195.95169"
|
||||||
|
r="87.84816" /><circle
|
||||||
|
style="fill:none;stroke:#3baf8a;stroke-width:1.46551;stroke-opacity:1"
|
||||||
|
id="path353-3"
|
||||||
|
cx="144.73466"
|
||||||
|
cy="195.95169"
|
||||||
|
r="94.565796" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="152.49817mm"
|
||||||
|
height="42.107525mm"
|
||||||
|
viewBox="0 0 152.49817 42.107525"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="logo_dark.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="1.829812"
|
||||||
|
inkscape:cx="150.56191"
|
||||||
|
inkscape:cy="-4.6452859"
|
||||||
|
inkscape:window-width="2318"
|
||||||
|
inkscape:window-height="1365"
|
||||||
|
inkscape:window-x="4768"
|
||||||
|
inkscape:window-y="177"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="text9525" /><defs
|
||||||
|
id="defs2" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-25.834871,-46.19669)"><g
|
||||||
|
aria-label="SUCH "
|
||||||
|
id="text9525"
|
||||||
|
style="font-size:56.4444px;letter-spacing:0.637646px;fill:#39bfd7;stroke-width:2"><path
|
||||||
|
d="m 58.290401,59.122465 c -0.112889,-4.28978 -1.580443,-7.450667 -4.402663,-9.595554 -2.878664,-2.144887 -6.54755,-3.217331 -11.063103,-3.217331 -4.684885,0 -8.410215,1.128888 -11.232435,3.273775 -2.82222,2.201332 -4.23333,5.136441 -4.23333,8.80533 0,1.12889 0.112888,2.14489 0.39511,3.10444 0.282222,0.95956 0.620889,1.74978 0.959555,2.42711 0.338667,0.67734 0.846666,1.29822 1.636888,1.91911 0.790222,0.56445 1.41111,1.07245 1.975554,1.41111 0.508,0.33867 1.354666,0.67734 2.483554,1.12889 1.128888,0.39511 1.975554,0.67733 2.483553,0.84667 0.564444,0.16933 1.467555,0.45155 2.82222,0.84666 0.169333,0 0.338667,0.0564 0.508,0.11289 0.225777,0.0564 0.338666,0.11289 0.451555,0.16933 0.112889,0 0.282222,0.0564 0.508,0.11289 3.951108,1.12889 6.491106,1.97556 7.732883,2.65289 1.241776,0.62089 1.80622,1.63689 1.80622,3.048 0,1.63689 -0.620888,2.93511 -1.975554,3.89466 -1.354665,0.95956 -3.160886,1.46756 -5.475107,1.46756 -5.870217,0 -8.918215,-2.54 -9.087548,-7.56355 h -8.748882 c 0.05644,4.51555 1.636887,8.07155 4.684885,10.5551 3.047998,2.48355 7.337772,3.78177 12.756435,3.78177 2.144887,0 4.176885,-0.28222 6.095995,-0.73377 1.91911,-0.45156 3.668886,-1.18534 5.249329,-2.20133 1.580443,-1.016 2.878665,-2.37067 3.781775,-4.064 0.959555,-1.69333 1.41111,-3.72533 1.41111,-5.98311 0,-3.16088 -0.959555,-5.58799 -2.878664,-7.28133 -1.91911,-1.69333 -4.910663,-3.10444 -8.861771,-4.23333 -0.903111,-0.28222 -1.91911,-0.56444 -3.160887,-0.84666 -1.185332,-0.33867 -2.088443,-0.56445 -2.765775,-0.73378 -0.677333,-0.22578 -1.41111,-0.45155 -2.201332,-0.67733 -0.790222,-0.22578 -1.41111,-0.45156 -1.862665,-0.67733 -0.395111,-0.22578 -0.790222,-0.45156 -1.241777,-0.79023 -0.395111,-0.33866 -0.677333,-0.67733 -0.846666,-1.016 -0.112889,-0.39511 -0.225778,-0.84666 -0.225778,-1.298221 0,-1.467555 0.620889,-2.596443 1.749777,-3.386664 1.128888,-0.790222 2.709331,-1.185333 4.741329,-1.185333 4.74133,0 7.281328,1.975554 7.619995,5.983108 z"
|
||||||
|
style="-inkscape-font-specification:'Helvetica Neue Bold';fill:#3f9f68;fill-opacity:1"
|
||||||
|
id="path2392" /><path
|
||||||
|
d="m 98.45513,46.796413 h -8.805327 v 24.045315 c 0,3.27378 -0.620888,5.70088 -1.749776,7.28133 -1.128888,1.58044 -3.273775,2.37066 -6.434662,2.37066 -3.273775,0 -5.531551,-0.79022 -6.773328,-2.37066 -1.241777,-1.58045 -1.862665,-4.00755 -1.862665,-7.28133 V 46.796413 h -8.805327 v 24.045315 c 0,5.75733 1.354666,10.0471 4.176886,12.81288 2.82222,2.76577 7.168439,4.12044 13.151545,4.12044 5.870218,0 10.159992,-1.35467 12.925768,-4.064 2.765776,-2.70933 4.176886,-6.9991 4.176886,-12.75643 z"
|
||||||
|
style="-inkscape-font-specification:'Helvetica Neue Bold';fill:#ea62a0;fill-opacity:1"
|
||||||
|
id="path2394" /><path
|
||||||
|
d="m 140.08742,71.766005 h -8.74888 c -0.79022,6.03955 -3.83822,9.0311 -9.20044,9.0311 -3.27377,0 -5.81377,-1.18533 -7.73288,-3.66888 -1.91911,-2.48356 -2.87866,-5.75733 -2.87866,-9.93422 0,-2.37066 0.33866,-4.51555 1.01599,-6.54755 0.67734,-1.97555 1.80623,-3.668885 3.49956,-5.02355 1.69333,-1.354666 3.72533,-2.031999 6.20888,-2.031999 2.37067,0 4.28978,0.564444 5.87022,1.806221 1.58044,1.241777 2.59644,2.935108 3.048,5.192888 h 8.63599 c -0.33867,-4.176889 -2.14489,-7.619997 -5.36222,-10.329328 -3.27378,-2.709332 -7.28133,-4.063997 -12.02266,-4.063997 -5.9831,0 -10.78088,1.975554 -14.39332,5.983106 -3.61244,3.951108 -5.36222,8.974659 -5.36222,15.014209 0,6.32178 1.74978,11.45822 5.30578,15.29644 3.556,3.83821 8.24088,5.81377 14.1111,5.81377 5.36222,0 9.59555,-1.524 12.69999,-4.45911 3.048,-2.99155 4.85422,-6.99911 5.30577,-12.0791 z"
|
||||||
|
style="-inkscape-font-specification:'Helvetica Neue Bold'"
|
||||||
|
id="path2396" /><path
|
||||||
|
d="m 178.33304,47.586633 h -8.80533 V 62.713738 H 153.10239 V 47.586633 h -8.80533 v 40.188415 h 8.80533 v -17.32843 h 16.42532 v 17.32843 h 8.80533 z"
|
||||||
|
style="-inkscape-font-specification:'Helvetica Neue Bold';fill:#5cc7a5;fill-opacity:1"
|
||||||
|
id="path2398" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="374.69867mm"
|
||||||
|
height="102.70798mm"
|
||||||
|
viewBox="0 0 374.69867 102.70798"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="logo_dark_w_name.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.64693625"
|
||||||
|
inkscape:cx="680.90171"
|
||||||
|
inkscape:cy="-139.89014"
|
||||||
|
inkscape:window-width="2318"
|
||||||
|
inkscape:window-height="1365"
|
||||||
|
inkscape:window-x="4768"
|
||||||
|
inkscape:window-y="177"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(72.188728,-54.96956)"><g
|
||||||
|
id="g464"
|
||||||
|
transform="translate(0,-102.13792)"><rect
|
||||||
|
style="fill:#5cc7a5;fill-opacity:1;stroke-width:2"
|
||||||
|
id="rect2694"
|
||||||
|
width="10.037269"
|
||||||
|
height="101.95406"
|
||||||
|
x="-72.188728"
|
||||||
|
y="157.86139"
|
||||||
|
ry="1.4290794" /><rect
|
||||||
|
style="fill:#39bfd7;fill-opacity:1;stroke-width:2"
|
||||||
|
id="rect2694-3"
|
||||||
|
width="10.037269"
|
||||||
|
height="101.95406"
|
||||||
|
x="-64.071434"
|
||||||
|
y="157.86139"
|
||||||
|
ry="1.4290794" /><rect
|
||||||
|
style="fill:#39bfd7;fill-opacity:1;stroke-width:1.6256"
|
||||||
|
id="rect2694-3-2"
|
||||||
|
width="6.6310406"
|
||||||
|
height="101.95406"
|
||||||
|
x="-64.071434"
|
||||||
|
y="157.86139"
|
||||||
|
ry="0" /></g><g
|
||||||
|
id="g468"
|
||||||
|
transform="translate(0,-102.28801)"><rect
|
||||||
|
style="fill:#5cc7a5;fill-opacity:1;stroke-width:2.02482"
|
||||||
|
id="rect2694-6"
|
||||||
|
width="17.863615"
|
||||||
|
height="58.716511"
|
||||||
|
x="-40.229259"
|
||||||
|
y="201.24904"
|
||||||
|
ry="0.8230232" /><rect
|
||||||
|
style="fill:#39bfd7;fill-opacity:1;stroke-width:1.78573"
|
||||||
|
id="rect2694-6-7"
|
||||||
|
width="17.863615"
|
||||||
|
height="45.668602"
|
||||||
|
x="-40.30563"
|
||||||
|
y="157.67754"
|
||||||
|
ry="0.64013201"
|
||||||
|
inkscape:transform-center-x="3.412718"
|
||||||
|
inkscape:transform-center-y="23.898841" /></g><rect
|
||||||
|
style="fill:#3f9f68;fill-opacity:1;stroke-width:2.67119"
|
||||||
|
id="rect2694-6-5"
|
||||||
|
width="17.863615"
|
||||||
|
height="102.18733"
|
||||||
|
x="-8.6201973"
|
||||||
|
y="55.490204"
|
||||||
|
ry="1.4323492" /><g
|
||||||
|
id="g472"
|
||||||
|
transform="translate(0,-102.85913)"><rect
|
||||||
|
style="fill:#ea62a0;fill-opacity:1;stroke-width:2.14284"
|
||||||
|
id="rect2694-6-3"
|
||||||
|
width="17.863615"
|
||||||
|
height="65.760574"
|
||||||
|
x="22.950037"
|
||||||
|
y="157.82869"
|
||||||
|
ry="0.92175913" /><rect
|
||||||
|
style="fill:#39bfd7;fill-opacity:1;stroke-width:1.8415"
|
||||||
|
id="rect2694-6-56"
|
||||||
|
width="17.863615"
|
||||||
|
height="48.565922"
|
||||||
|
x="22.929722"
|
||||||
|
y="211.97075"
|
||||||
|
ry="0.68074346" /></g><text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:56.4444px;line-height:55.5625px;text-align:start;letter-spacing:0.637646px;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke-width:2"
|
||||||
|
x="58.101341"
|
||||||
|
y="98.397858"
|
||||||
|
id="text9525"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan9523"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:56.4444px;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Bold';text-align:start;text-anchor:start;fill:#ffffff;stroke-width:2"
|
||||||
|
x="58.101341"
|
||||||
|
y="98.397858">Such</tspan><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:56.4444px;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Bold';text-align:start;text-anchor:start;fill:#ffffff;stroke-width:2"
|
||||||
|
x="58.101341"
|
||||||
|
y="154.07214"
|
||||||
|
id="tspan9527">Software</tspan></text></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="152.49817mm"
|
||||||
|
height="42.107525mm"
|
||||||
|
viewBox="0 0 152.49817 42.107525"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="logo_light.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.587745"
|
||||||
|
inkscape:cx="328.66453"
|
||||||
|
inkscape:cy="69.75185"
|
||||||
|
inkscape:window-width="1960"
|
||||||
|
inkscape:window-height="1365"
|
||||||
|
inkscape:window-x="5398"
|
||||||
|
inkscape:window-y="277"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="text9525" /><defs
|
||||||
|
id="defs2" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-25.834871,-46.19669)"><g
|
||||||
|
aria-label="SUCH "
|
||||||
|
id="text9525"
|
||||||
|
style="font-size:56.4444px;letter-spacing:0.637646px;fill:#39bfd7;stroke-width:2"><path
|
||||||
|
d="m 58.290401,59.122465 c -0.112889,-4.28978 -1.580443,-7.450667 -4.402663,-9.595554 -2.878664,-2.144887 -6.54755,-3.217331 -11.063103,-3.217331 -4.684885,0 -8.410215,1.128888 -11.232435,3.273775 -2.82222,2.201332 -4.23333,5.136441 -4.23333,8.80533 0,1.12889 0.112888,2.14489 0.39511,3.10444 0.282222,0.95956 0.620889,1.74978 0.959555,2.42711 0.338667,0.67734 0.846666,1.29822 1.636888,1.91911 0.790222,0.56445 1.41111,1.07245 1.975554,1.41111 0.508,0.33867 1.354666,0.67734 2.483554,1.12889 1.128888,0.39511 1.975554,0.67733 2.483553,0.84667 0.564444,0.16933 1.467555,0.45155 2.82222,0.84666 0.169333,0 0.338667,0.0564 0.508,0.11289 0.225777,0.0564 0.338666,0.11289 0.451555,0.16933 0.112889,0 0.282222,0.0564 0.508,0.11289 3.951108,1.12889 6.491106,1.97556 7.732883,2.65289 1.241776,0.62089 1.80622,1.63689 1.80622,3.048 0,1.63689 -0.620888,2.93511 -1.975554,3.89466 -1.354665,0.95956 -3.160886,1.46756 -5.475107,1.46756 -5.870217,0 -8.918215,-2.54 -9.087548,-7.56355 h -8.748882 c 0.05644,4.51555 1.636887,8.07155 4.684885,10.5551 3.047998,2.48355 7.337772,3.78177 12.756435,3.78177 2.144887,0 4.176885,-0.28222 6.095995,-0.73377 1.91911,-0.45156 3.668886,-1.18534 5.249329,-2.20133 1.580443,-1.016 2.878665,-2.37067 3.781775,-4.064 0.959555,-1.69333 1.41111,-3.72533 1.41111,-5.98311 0,-3.16088 -0.959555,-5.58799 -2.878664,-7.28133 -1.91911,-1.69333 -4.910663,-3.10444 -8.861771,-4.23333 -0.903111,-0.28222 -1.91911,-0.56444 -3.160887,-0.84666 -1.185332,-0.33867 -2.088443,-0.56445 -2.765775,-0.73378 -0.677333,-0.22578 -1.41111,-0.45155 -2.201332,-0.67733 -0.790222,-0.22578 -1.41111,-0.45156 -1.862665,-0.67733 -0.395111,-0.22578 -0.790222,-0.45156 -1.241777,-0.79023 -0.395111,-0.33866 -0.677333,-0.67733 -0.846666,-1.016 -0.112889,-0.39511 -0.225778,-0.84666 -0.225778,-1.298221 0,-1.467555 0.620889,-2.596443 1.749777,-3.386664 1.128888,-0.790222 2.709331,-1.185333 4.741329,-1.185333 4.74133,0 7.281328,1.975554 7.619995,5.983108 z"
|
||||||
|
style="-inkscape-font-specification:'Helvetica Neue Bold';fill:#286442;fill-opacity:1"
|
||||||
|
id="path2392" /><path
|
||||||
|
d="m 98.45513,46.796413 h -8.805327 v 24.045315 c 0,3.27378 -0.620888,5.70088 -1.749776,7.28133 -1.128888,1.58044 -3.273775,2.37066 -6.434662,2.37066 -3.273775,0 -5.531551,-0.79022 -6.773328,-2.37066 -1.241777,-1.58045 -1.862665,-4.00755 -1.862665,-7.28133 V 46.796413 h -8.805327 v 24.045315 c 0,5.75733 1.354666,10.0471 4.176886,12.81288 2.82222,2.76577 7.168439,4.12044 13.151545,4.12044 5.870218,0 10.159992,-1.35467 12.925768,-4.064 2.765776,-2.70933 4.176886,-6.9991 4.176886,-12.75643 z"
|
||||||
|
style="-inkscape-font-specification:'Helvetica Neue Bold';fill:#ea62a0;fill-opacity:1"
|
||||||
|
id="path2394" /><path
|
||||||
|
d="m 140.08742,71.766005 h -8.74888 c -0.79022,6.03955 -3.83822,9.0311 -9.20044,9.0311 -3.27377,0 -5.81377,-1.18533 -7.73288,-3.66888 -1.91911,-2.48356 -2.87866,-5.75733 -2.87866,-9.93422 0,-2.37066 0.33866,-4.51555 1.01599,-6.54755 0.67734,-1.97555 1.80623,-3.668885 3.49956,-5.02355 1.69333,-1.354666 3.72533,-2.031999 6.20888,-2.031999 2.37067,0 4.28978,0.564444 5.87022,1.806221 1.58044,1.241777 2.59644,2.935108 3.048,5.192888 h 8.63599 c -0.33867,-4.176889 -2.14489,-7.619997 -5.36222,-10.329328 -3.27378,-2.709332 -7.28133,-4.063997 -12.02266,-4.063997 -5.9831,0 -10.78088,1.975554 -14.39332,5.983106 -3.61244,3.951108 -5.36222,8.974659 -5.36222,15.014209 0,6.32178 1.74978,11.45822 5.30578,15.29644 3.556,3.83821 8.24088,5.81377 14.1111,5.81377 5.36222,0 9.59555,-1.524 12.69999,-4.45911 3.048,-2.99155 4.85422,-6.99911 5.30577,-12.0791 z"
|
||||||
|
style="-inkscape-font-specification:'Helvetica Neue Bold';fill:#3ca5d4;fill-opacity:1"
|
||||||
|
id="path2396" /><path
|
||||||
|
d="m 178.33304,47.586633 h -8.80533 V 62.713738 H 153.10239 V 47.586633 h -8.80533 v 40.188415 h 8.80533 v -17.32843 h 16.42532 v 17.32843 h 8.80533 z"
|
||||||
|
style="-inkscape-font-specification:'Helvetica Neue Bold';fill:#3baf8a;fill-opacity:1"
|
||||||
|
id="path2398" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="374.69867mm"
|
||||||
|
height="102.70798mm"
|
||||||
|
viewBox="0 0 374.69867 102.70798"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="logo_light_w_name.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.64693625"
|
||||||
|
inkscape:cx="257.36694"
|
||||||
|
inkscape:cy="186.26256"
|
||||||
|
inkscape:window-width="2318"
|
||||||
|
inkscape:window-height="1365"
|
||||||
|
inkscape:window-x="4768"
|
||||||
|
inkscape:window-y="177"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(72.188728,-54.96956)"><g
|
||||||
|
id="g464"
|
||||||
|
transform="translate(0,-102.13792)"><rect
|
||||||
|
style="fill:#3baf8a;fill-opacity:1;stroke-width:2"
|
||||||
|
id="rect2694"
|
||||||
|
width="10.037269"
|
||||||
|
height="101.95406"
|
||||||
|
x="-72.188728"
|
||||||
|
y="157.86139"
|
||||||
|
ry="1.4290794" /><rect
|
||||||
|
style="fill:#2190a4;fill-opacity:1;stroke-width:2"
|
||||||
|
id="rect2694-3"
|
||||||
|
width="10.037269"
|
||||||
|
height="101.95406"
|
||||||
|
x="-64.071434"
|
||||||
|
y="157.86139"
|
||||||
|
ry="1.4290794" /><rect
|
||||||
|
style="fill:#2190a4;fill-opacity:1;stroke-width:1.6256"
|
||||||
|
id="rect2694-3-2"
|
||||||
|
width="6.6310406"
|
||||||
|
height="101.95406"
|
||||||
|
x="-64.071434"
|
||||||
|
y="157.86139"
|
||||||
|
ry="0" /></g><g
|
||||||
|
id="g468"
|
||||||
|
transform="translate(0,-102.28801)"><rect
|
||||||
|
style="fill:#3baf8a;fill-opacity:1;stroke-width:2.02482"
|
||||||
|
id="rect2694-6"
|
||||||
|
width="17.863615"
|
||||||
|
height="58.716511"
|
||||||
|
x="-40.229259"
|
||||||
|
y="201.24904"
|
||||||
|
ry="0.8230232" /><rect
|
||||||
|
style="fill:#2190a4;fill-opacity:1;stroke-width:1.78573"
|
||||||
|
id="rect2694-6-7"
|
||||||
|
width="17.863615"
|
||||||
|
height="45.668602"
|
||||||
|
x="-40.30563"
|
||||||
|
y="157.67754"
|
||||||
|
ry="0.64013201"
|
||||||
|
inkscape:transform-center-x="3.412718"
|
||||||
|
inkscape:transform-center-y="23.898841" /></g><rect
|
||||||
|
style="fill:#286442;fill-opacity:1;stroke-width:2.67119"
|
||||||
|
id="rect2694-6-5"
|
||||||
|
width="17.863615"
|
||||||
|
height="102.18733"
|
||||||
|
x="-8.6201973"
|
||||||
|
y="55.490204"
|
||||||
|
ry="1.4323492" /><g
|
||||||
|
id="g472"
|
||||||
|
transform="translate(0,-102.85913)"><rect
|
||||||
|
style="fill:#ea62a0;fill-opacity:1;stroke-width:2.14284"
|
||||||
|
id="rect2694-6-3"
|
||||||
|
width="17.863615"
|
||||||
|
height="65.760574"
|
||||||
|
x="22.950037"
|
||||||
|
y="157.82869"
|
||||||
|
ry="0.92175913" /><rect
|
||||||
|
style="fill:#3ca5d4;fill-opacity:1;stroke-width:1.8415"
|
||||||
|
id="rect2694-6-56"
|
||||||
|
width="17.863615"
|
||||||
|
height="48.565922"
|
||||||
|
x="22.929722"
|
||||||
|
y="211.97075"
|
||||||
|
ry="0.68074346" /></g><text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:56.4444px;line-height:55.5625px;text-align:start;letter-spacing:0.637646px;text-anchor:start;fill:#000000;fill-opacity:1;stroke-width:2"
|
||||||
|
x="58.101341"
|
||||||
|
y="98.397858"
|
||||||
|
id="text9525"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan9523"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:56.4444px;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Bold';text-align:start;text-anchor:start;fill:#000000;stroke-width:2"
|
||||||
|
x="58.101341"
|
||||||
|
y="98.397858">Such</tspan><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:56.4444px;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Bold';text-align:start;text-anchor:start;fill:#000000;stroke-width:2"
|
||||||
|
x="58.101341"
|
||||||
|
y="154.07214"
|
||||||
|
id="tspan9527">Software</tspan></text></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -7,6 +7,11 @@
|
|||||||
"background_color": "#000000",
|
"background_color": "#000000",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"icons": [
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/images/favicon.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"src": "/images/favicon_192.png",
|
"src": "/images/favicon_192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { DynamicBackground } from "@/components/landing/dynamic-background";
|
|
||||||
import { ThemeToggle } from "@/components/theme-toggle";
|
|
||||||
import { FounderSignature } from "@/components/about/founder-signature";
|
import { FounderSignature } from "@/components/about/founder-signature";
|
||||||
import { SiteLogo } from "@/components/layout/site-logo";
|
import { Header } from "@/components/layout/header";
|
||||||
import Link from "next/link";
|
|
||||||
import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-icons/fa";
|
import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-icons/fa";
|
||||||
|
import { FaXTwitter, FaTelegram, FaReddit } from "react-icons/fa6";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
@@ -19,19 +17,9 @@ export const metadata: Metadata = {
|
|||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
return (
|
return (
|
||||||
<main className="relative flex flex-col items-center overflow-hidden">
|
<main className="relative flex flex-col items-center overflow-hidden">
|
||||||
|
<Header />
|
||||||
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-3xl sticky top-0 rounded-b-3xl border-b border-border/50">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<SiteLogo />
|
|
||||||
<nav className="hidden md:flex gap-6 text-sm font-medium">
|
|
||||||
<Link href="/services" className="hover:text-primary transition-colors">Services</Link>
|
|
||||||
<Link href="/about" className="text-primary transition-colors">About</Link>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<ThemeToggle />
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="z-10 w-full max-w-6xl mx-auto py-20 px-4">
|
<div id="main-content" className="z-10 w-full max-w-6xl mx-auto py-20 px-4">
|
||||||
<h1 className="text-4xl md:text-5xl font-bold mb-10 text-center">About Us</h1>
|
<h1 className="text-4xl md:text-5xl font-bold mb-10 text-center">About Us</h1>
|
||||||
|
|
||||||
<div className="section-container">
|
<div className="section-container">
|
||||||
@@ -78,8 +66,21 @@ export default function AboutPage() {
|
|||||||
<div className="flex flex-col justify-center items-center bg-muted/30 rounded-xl p-6">
|
<div className="flex flex-col justify-center items-center bg-muted/30 rounded-xl p-6">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h3 className="text-lg font-semibold mb-2">Connect</h3>
|
<h3 className="text-lg font-semibold mb-2">Connect</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">Find us on your favorite platforms</p>
|
<p className="text-sm text-muted-foreground mb-4">Find us on your favorite platforms</p>
|
||||||
{/* Social links are in the footer, but we can highlight them here or leave for footer */}
|
<div className="flex gap-6 justify-center">
|
||||||
|
<a href="https://x.com/such_software" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-primary transition-colors">
|
||||||
|
<FaXTwitter className="h-6 w-6" />
|
||||||
|
<span className="sr-only">X (Twitter)</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/j_winter_m" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-primary transition-colors">
|
||||||
|
<FaTelegram className="h-6 w-6" />
|
||||||
|
<span className="sr-only">Telegram</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/SuchSoftware" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-primary transition-colors">
|
||||||
|
<FaReddit className="h-6 w-6" />
|
||||||
|
<span className="sr-only">Reddit</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 300ms;
|
transition: all 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-container {
|
.section-container {
|
||||||
@apply mask-backdrop rounded-[2.5rem] p-12 md:p-20 text-card-foreground;
|
@apply mask-backdrop rounded-[2.5rem] p-12 md:p-20 text-card-foreground;
|
||||||
}
|
}
|
||||||
@@ -95,8 +95,20 @@
|
|||||||
.glass-card {
|
.glass-card {
|
||||||
@apply mask-backdrop rounded-[2rem] p-10 text-card-foreground;
|
@apply mask-backdrop rounded-[2rem] p-10 text-card-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glass-card:hover {
|
.glass-card:hover {
|
||||||
border-color: hsl(var(--primary) / 0.3);
|
border-color: hsl(var(--primary) / 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Respect users who prefer reduced motion */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ export const metadata: Metadata = {
|
|||||||
description: "Next-generation web applications engineered for scale, compliance, and performance.",
|
description: "Next-generation web applications engineered for scale, compliance, and performance.",
|
||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
|
{ url: "/images/favicon.svg", type: "image/svg+xml" },
|
||||||
{ url: "/images/favicon_192.png", sizes: "192x192", type: "image/png" },
|
{ url: "/images/favicon_192.png", sizes: "192x192", type: "image/png" },
|
||||||
],
|
],
|
||||||
shortcut: "/images/favicon_192.png",
|
shortcut: "/images/favicon.svg",
|
||||||
apple: [
|
apple: [
|
||||||
{ url: "/images/favicon_192.png", sizes: "192x192", type: "image/png" },
|
{ url: "/images/favicon_192.png", sizes: "192x192", type: "image/png" },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,30 +1,17 @@
|
|||||||
import { DynamicBackground } from "@/components/landing/dynamic-background";
|
|
||||||
import { ThemeToggle } from "@/components/theme-toggle";
|
|
||||||
import { PortfolioGrid } from "@/components/portfolio/bento-grid";
|
import { PortfolioGrid } from "@/components/portfolio/bento-grid";
|
||||||
import { ContactForm } from "@/components/contact/contact-form";
|
import { ContactForm } from "@/components/contact/contact-form";
|
||||||
import { FounderSignature } from "@/components/about/founder-signature";
|
import { FounderSignature } from "@/components/about/founder-signature";
|
||||||
import { ServicesPreview } from "@/components/landing/services-preview";
|
import { ServicesPreview } from "@/components/landing/services-preview";
|
||||||
import { SiteLogo } from "@/components/layout/site-logo";
|
import { Header } from "@/components/layout/header";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Button } from "@repo/ui/components/button";
|
import { Button } from "@repo/ui/components/button";
|
||||||
import { Card } from "@repo/ui/components/card";
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<main className="relative flex flex-col items-center justify-between overflow-hidden">
|
<main className="relative flex flex-col items-center justify-between overflow-hidden">
|
||||||
|
<Header />
|
||||||
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-3xl sticky top-0 rounded-b-3xl border-b border-border/50">
|
|
||||||
<div className="flex items-center gap-2">
|
<div id="main-content" className="z-10 w-full max-w-7xl mx-auto flex flex-col items-center pt-20 pb-10 px-4 text-center">
|
||||||
<SiteLogo />
|
|
||||||
<nav className="hidden md:flex gap-6 text-sm font-medium">
|
|
||||||
<Link href="/services" className="hover:text-primary transition-colors">Services</Link>
|
|
||||||
<Link href="/about" className="hover:text-primary transition-colors">About</Link>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<ThemeToggle />
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="z-10 w-full max-w-7xl mx-auto flex flex-col items-center pt-20 pb-10 px-4 text-center">
|
|
||||||
<h1 className="text-5xl md:text-7xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-emerald-500 pb-2">
|
<h1 className="text-5xl md:text-7xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-emerald-500 pb-2">
|
||||||
Digital Craftsmanship
|
Digital Craftsmanship
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { DataGrid } from "@repo/ui/procedural/data-grid";
|
import { DataGrid } from "@repo/ui/procedural/data-grid";
|
||||||
import { ThemeToggle } from "@/components/theme-toggle";
|
|
||||||
import { Button } from "@repo/ui/components/button";
|
import { Button } from "@repo/ui/components/button";
|
||||||
import { SiteLogo } from "@/components/layout/site-logo";
|
import { Header } from "@/components/layout/header";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Card } from "@repo/ui/components/card";
|
import { Card } from "@repo/ui/components/card";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
@@ -19,22 +18,10 @@ export const metadata: Metadata = {
|
|||||||
export default function ServicesPage() {
|
export default function ServicesPage() {
|
||||||
return (
|
return (
|
||||||
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground overflow-hidden">
|
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground overflow-hidden">
|
||||||
|
|
||||||
{/* Option B: Data Grid Background for Technical Feel */}
|
|
||||||
<DataGrid />
|
<DataGrid />
|
||||||
|
<Header />
|
||||||
|
|
||||||
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-3xl sticky top-0 border-b border-border/50 rounded-b-3xl">
|
<div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4">
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<SiteLogo />
|
|
||||||
<nav className="hidden md:flex gap-6 text-sm font-medium">
|
|
||||||
<Link href="/services" className="text-primary transition-colors">Services</Link>
|
|
||||||
<Link href="/about" className="hover:text-primary transition-colors">About</Link>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<ThemeToggle />
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="z-10 w-full max-w-5xl mx-auto py-20 px-4">
|
|
||||||
{/* Context Header */}
|
{/* Context Header */}
|
||||||
<div className="mb-20">
|
<div className="mb-20">
|
||||||
<div className="text-left mb-8 section-container border-l-4 border-l-emerald-500">
|
<div className="text-left mb-8 section-container border-l-4 border-l-emerald-500">
|
||||||
@@ -70,8 +57,8 @@ export default function ServicesPage() {
|
|||||||
<Card className="glass-card hover:border-indigo-500/30">
|
<Card className="glass-card hover:border-indigo-500/30">
|
||||||
<h3 className="text-xl font-bold mb-3">Zero Capital Upfront</h3>
|
<h3 className="text-xl font-bold mb-3">Zero Capital Upfront</h3>
|
||||||
<p className="text-base text-muted-foreground leading-relaxed">
|
<p className="text-base text-muted-foreground leading-relaxed">
|
||||||
Our "Toast" style partnership means we win when you win. Low friction onboarding
|
Our revenue-share partnership means no setup fees or monthly subscriptions—we
|
||||||
with performance-based incentives.
|
only earn when you're making sales.
|
||||||
</p>
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="glass-card hover:border-indigo-500/30">
|
<Card className="glass-card hover:border-indigo-500/30">
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ export const FounderSignature = () => {
|
|||||||
{/* Left Column: Bio */}
|
{/* Left Column: Bio */}
|
||||||
<div className="space-y-8 text-left section-container">
|
<div className="space-y-8 text-left section-container">
|
||||||
<div className="flex items-center gap-6 relative z-10">
|
<div className="flex items-center gap-6 relative z-10">
|
||||||
<div className="relative w-24 h-24 rounded-full overflow-hidden border-2 border-primary/10 bg-white dark:bg-zinc-900 shadow-xl ring-4 ring-primary/5">
|
<div className="relative w-24 h-24 rounded-full overflow-hidden border-2 border-primary/10 bg-white dark:bg-zinc-900 shadow-xl ring-4 ring-primary/5 flex items-center justify-center">
|
||||||
<Image
|
<Image
|
||||||
src="/images/logo_w_circle_trans.jpg"
|
src="/images/favicon.svg"
|
||||||
alt="Such Software Icon"
|
alt="Such Software Icon"
|
||||||
fill
|
width={72}
|
||||||
className="object-cover scale-[1.02]"
|
height={72}
|
||||||
|
className="object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -23,13 +24,13 @@ export const FounderSignature = () => {
|
|||||||
|
|
||||||
<div className="prose dark:prose-invert max-w-none">
|
<div className="prose dark:prose-invert max-w-none">
|
||||||
<p className="text-muted-foreground leading-relaxed italic border-l-4 border-primary/20 pl-6 text-lg">
|
<p className="text-muted-foreground leading-relaxed italic border-l-4 border-primary/20 pl-6 text-lg">
|
||||||
"During my three years at <span className="text-foreground font-semibold">Cecil College</span>, I didn't just teach web engineering—I re-engineered how it was taught.
|
"During my three years at <span className="text-foreground font-semibold">Cecil College</span>, I didn't just teach engineering—I re-engineered how it was taught.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-muted-foreground leading-relaxed mt-6">
|
<p className="text-muted-foreground leading-relaxed mt-6">
|
||||||
Transitioning the curriculum toward from PDFs and Powerpoints to web-based content, I focused on transforming complex technical lessons into highly accessible HTML using <span className="text-foreground font-semibold underline decoration-primary/50">Quarto markdown</span> as our source. This allowed us to consistently generate semantic content including accessible equations and guaranteed descriptive alt text for every visual asset.
|
Transitioning the curriculum from PDFs and Powerpoints to web-based content, I focused on transforming complex technical lessons into highly accessible HTML using <span className="text-foreground font-semibold underline decoration-primary/50">Quarto markdown</span>. This allowed us to consistently generate semantic content including accessible equations and guaranteed descriptive alt text for every visual asset.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-muted-foreground leading-relaxed mt-4 italic border-l-4 border-primary/20 pl-6 text-lg">
|
<p className="text-muted-foreground leading-relaxed mt-4 italic border-l-4 border-primary/20 pl-6 text-lg">
|
||||||
This wasn't just academic theory; it was a rigorous discipline of semantic architecture, ARIA compliance, and cognitive load management. Today, I bring that same technical rigor to Such Software’s commercial projects. We don't retroactively fix accessibility; we architect for it from line one."
|
This wasn't just theory; it was a methodical implementation of semantic architecture, ARIA compliance, and cognitive load management. Today, I bring that same technical rigor to Such Software’s commercial projects. We don't retroactively fix accessibility; we architect for it from line one."
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,18 +62,18 @@ export const FounderSignature = () => {
|
|||||||
<span className="text-emerald-500 text-[10px]">03</span>
|
<span className="text-emerald-500 text-[10px]">03</span>
|
||||||
Core Competencies
|
Core Competencies
|
||||||
</h4>
|
</h4>
|
||||||
<ul className="grid grid-cols-1 gap-2 font-mono text-[11px] uppercase tracking-wider text-muted-foreground/80">
|
<ul className="grid grid-cols-1 gap-2 font-mono text-xs uppercase tracking-wider text-muted-foreground">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="text-emerald-500 text-sm">✓</span> WCAG 2.2 AA Compliance
|
<span className="text-emerald-500">✓</span> WCAG 2.2 AA Compliance
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="text-emerald-500 text-sm">✓</span> Section 508 & ADA
|
<span className="text-emerald-500">✓</span> Section 508 & ADA
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="text-emerald-500 text-sm">✓</span> Semantic HTML5 Architecture
|
<span className="text-emerald-500">✓</span> Semantic HTML5 Architecture
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="text-emerald-500 text-sm">✓</span> Playwright A11y Automation
|
<span className="text-emerald-500">✓</span> Playwright A11y Automation
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useFormState, useFormStatus } from 'react-dom';
|
import { useActionState } from 'react';
|
||||||
|
import { useFormStatus } from 'react-dom';
|
||||||
import { submitContactForm } from '@/actions/submit-contact';
|
import { submitContactForm } from '@/actions/submit-contact';
|
||||||
import { TurnstileWidget } from './turnstile-widget';
|
import { TurnstileWidget } from './turnstile-widget';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { Button } from '@repo/ui/components/button';
|
import { Button } from '@repo/ui/components/button';
|
||||||
|
|
||||||
function SubmitButton() {
|
function SubmitButton() {
|
||||||
const { pending } = useFormStatus();
|
const { pending } = useFormStatus();
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full md:w-64 rounded-xl text-lg font-semibold"
|
className="w-full md:w-64 rounded-xl text-lg font-semibold"
|
||||||
>
|
>
|
||||||
@@ -20,12 +21,22 @@ function SubmitButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ContactForm() {
|
export function ContactForm() {
|
||||||
const [state, formAction] = useFormState(submitContactForm, { success: false, message: '' });
|
const [state, formAction] = useActionState(submitContactForm, { success: false, message: '' });
|
||||||
const [token, setToken] = useState<string>('');
|
const [token, setToken] = useState<string>('');
|
||||||
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
|
// Reset form on successful submission
|
||||||
|
useEffect(() => {
|
||||||
|
if (state.success && formRef.current) {
|
||||||
|
formRef.current.reset();
|
||||||
|
setToken('');
|
||||||
|
}
|
||||||
|
}, [state.success]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
action={formAction}
|
ref={formRef}
|
||||||
|
action={formAction}
|
||||||
className="space-y-8 w-full max-w-6xl mx-auto section-container"
|
className="space-y-8 w-full max-w-6xl mx-auto section-container"
|
||||||
>
|
>
|
||||||
<div className="absolute top-0 right-0 w-64 h-64 bg-primary/5 rounded-full blur-3xl -mr-32 -mt-32" />
|
<div className="absolute top-0 right-0 w-64 h-64 bg-primary/5 rounded-full blur-3xl -mr-32 -mt-32" />
|
||||||
@@ -41,40 +52,46 @@ export function ContactForm() {
|
|||||||
<div className="grid md:grid-cols-2 gap-8 relative z-10">
|
<div className="grid md:grid-cols-2 gap-8 relative z-10">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label htmlFor="name" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Full Name</label>
|
<label htmlFor="name" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Full Name</label>
|
||||||
<input
|
<input
|
||||||
name="name"
|
name="name"
|
||||||
id="name"
|
id="name"
|
||||||
placeholder="Jamie Doe"
|
placeholder="Jamie Doe"
|
||||||
required
|
required
|
||||||
className="w-full p-4 border-2 border-border/50 rounded-2xl bg-background/50 focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg"
|
aria-describedby={state?.errors?.name ? "name-error" : undefined}
|
||||||
|
aria-invalid={state?.errors?.name ? "true" : undefined}
|
||||||
|
className="w-full p-4 border-2 border-border/50 rounded-2xl bg-background/50 focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg"
|
||||||
/>
|
/>
|
||||||
{state?.errors?.name && <p className="text-red-500 text-sm px-1">{state.errors.name}</p>}
|
{state?.errors?.name && <p id="name-error" className="text-red-500 text-sm px-1" role="alert">{state.errors.name}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label htmlFor="email" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Work Email</label>
|
<label htmlFor="email" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Work Email</label>
|
||||||
<input
|
<input
|
||||||
name="email"
|
name="email"
|
||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="jamie@example.com"
|
placeholder="jamie@example.com"
|
||||||
required
|
required
|
||||||
className="w-full p-4 border-2 border-border/50 rounded-2xl bg-background/50 focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg"
|
aria-describedby={state?.errors?.email ? "email-error" : undefined}
|
||||||
|
aria-invalid={state?.errors?.email ? "true" : undefined}
|
||||||
|
className="w-full p-4 border-2 border-border/50 rounded-2xl bg-background/50 focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg"
|
||||||
/>
|
/>
|
||||||
{state?.errors?.email && <p className="text-red-500 text-sm px-1">{state.errors.email}</p>}
|
{state?.errors?.email && <p id="email-error" className="text-red-500 text-sm px-1" role="alert">{state.errors.email}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3 relative z-10">
|
<div className="space-y-3 relative z-10">
|
||||||
<label htmlFor="message" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Project Details</label>
|
<label htmlFor="message" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Project Details</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="message"
|
name="message"
|
||||||
id="message"
|
id="message"
|
||||||
placeholder="What are you building? Mention any specific constraints, timelines, or technologies..."
|
placeholder="What are you building? Mention any specific constraints, timelines, or technologies..."
|
||||||
required
|
required
|
||||||
className="w-full p-6 border-2 border-border/50 rounded-2xl bg-background/50 h-56 resize-none focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg leading-relaxed"
|
aria-describedby={state?.errors?.message ? "message-error" : undefined}
|
||||||
|
aria-invalid={state?.errors?.message ? "true" : undefined}
|
||||||
|
className="w-full p-6 border-2 border-border/50 rounded-2xl bg-background/50 h-56 resize-none focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg leading-relaxed"
|
||||||
/>
|
/>
|
||||||
{state?.errors?.message && <p className="text-red-500 text-sm px-1">{state.errors.message}</p>}
|
{state?.errors?.message && <p id="message-error" className="text-red-500 text-sm px-1" role="alert">{state.errors.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="token" value={token} />
|
<input type="hidden" name="token" value={token} />
|
||||||
@@ -92,13 +109,17 @@ export function ContactForm() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state?.message && (
|
{state?.message && (
|
||||||
<div className={`mt-8 p-6 rounded-2xl text-base border-l-4 relative z-10 ${
|
<div
|
||||||
state.success
|
role="status"
|
||||||
? "bg-emerald-500/10 border-emerald-500 text-emerald-700 dark:text-emerald-400"
|
aria-live="polite"
|
||||||
: "bg-red-500/10 border-red-500 text-red-700 dark:text-red-400"
|
className={`mt-8 p-6 rounded-2xl text-base border-l-4 relative z-10 ${
|
||||||
}`}>
|
state.success
|
||||||
|
? "bg-emerald-500/10 border-emerald-500 text-emerald-700 dark:text-emerald-400"
|
||||||
|
: "bg-red-500/10 border-red-500 text-red-700 dark:text-red-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-xl">{state.success ? "✓" : "⚠"}</span>
|
<span className="text-xl" aria-hidden="true">{state.success ? "✓" : "⚠"}</span>
|
||||||
<p className="font-medium">{state.message}</p>
|
<p className="font-medium">{state.message}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,19 +5,31 @@ import { NebulaField } from "@repo/ui/procedural/nebula-field";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export function DynamicBackground() {
|
export function DynamicBackground() {
|
||||||
const { theme } = useTheme();
|
const { resolvedTheme, theme, systemTheme } = useTheme();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!mounted) return null;
|
// Debug on first load
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[DynamicBackground] mounted:', mounted, 'theme:', theme, 'resolvedTheme:', resolvedTheme, 'systemTheme:', systemTheme);
|
||||||
|
}, [mounted, theme, resolvedTheme, systemTheme]);
|
||||||
|
|
||||||
|
// Don't render until mounted (standard hydration fix)
|
||||||
|
if (!mounted) {
|
||||||
|
return <div className="fixed inset-0 z-0 h-screen w-screen" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After mount, determine the theme - fall back to system preference or 'dark'
|
||||||
|
const effectiveTheme = resolvedTheme || systemTheme || 'dark';
|
||||||
|
const themeMode = effectiveTheme === 'dark' ? 'dark' : 'light';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NebulaField
|
<NebulaField
|
||||||
className="fixed inset-0 z-0 h-screen w-screen"
|
className="fixed inset-0 z-0 h-screen w-screen"
|
||||||
themeMode={theme === 'dark' ? 'dark' : 'light'}
|
themeMode={themeMode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { SiteLogo } from "./site-logo";
|
||||||
|
import { ThemeToggle } from "@/components/theme-toggle";
|
||||||
|
|
||||||
|
export function Header() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground focus:rounded-lg focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
|
>
|
||||||
|
Skip to main content
|
||||||
|
</a>
|
||||||
|
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-3xl sticky top-0 rounded-b-3xl border-b border-border/50">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<SiteLogo />
|
||||||
|
<nav className="hidden md:flex gap-6 text-sm font-medium" aria-label="Main navigation">
|
||||||
|
<Link
|
||||||
|
href="/services"
|
||||||
|
className={pathname === "/services" ? "text-primary" : "hover:text-primary transition-colors"}
|
||||||
|
>
|
||||||
|
Services
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/about"
|
||||||
|
className={pathname === "/about" ? "text-primary" : "hover:text-primary transition-colors"}
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<ThemeToggle />
|
||||||
|
</header>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,10 +6,10 @@ import Link from "next/link";
|
|||||||
export function SiteLogo() {
|
export function SiteLogo() {
|
||||||
return (
|
return (
|
||||||
<Link href="/" className="flex items-center gap-3 group">
|
<Link href="/" className="flex items-center gap-3 group">
|
||||||
<div className="relative w-52 h-14 lg:w-64 lg:h-16">
|
<div className="relative w-40 h-11 lg:w-48 lg:h-12">
|
||||||
{/* Dark mode logo (white text) */}
|
{/* Dark mode logo (white text) */}
|
||||||
<Image
|
<Image
|
||||||
src="/images/logo_dark_w_name_trans.png"
|
src="/images/logo_dark_w_name.svg"
|
||||||
alt="Such Software"
|
alt="Such Software"
|
||||||
fill
|
fill
|
||||||
className="object-contain hidden dark:block"
|
className="object-contain hidden dark:block"
|
||||||
@@ -17,7 +17,7 @@ export function SiteLogo() {
|
|||||||
/>
|
/>
|
||||||
{/* Light mode logo (black text) */}
|
{/* Light mode logo (black text) */}
|
||||||
<Image
|
<Image
|
||||||
src="/images/logo_light_w_name_trans.png"
|
src="/images/logo_light_w_name.svg"
|
||||||
alt="Such Software"
|
alt="Such Software"
|
||||||
fill
|
fill
|
||||||
className="object-contain block dark:hidden"
|
className="object-contain block dark:hidden"
|
||||||
|
|||||||
@@ -1,81 +1,90 @@
|
|||||||
import { Card } from "@repo/ui/components/card";
|
import { Card } from "@repo/ui/components/card";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export const PortfolioGrid = () => {
|
export const PortfolioGrid = () => {
|
||||||
return (
|
return (
|
||||||
<section className="grid grid-cols-1 md:grid-cols-4 auto-rows-[300px] gap-6 w-full max-w-7xl mx-auto p-4 z-10 relative">
|
<section className="grid grid-cols-1 md:grid-cols-4 auto-rows-[300px] gap-6 w-full max-w-7xl mx-auto p-4 z-10 relative">
|
||||||
{/* Hero Project: r/CryptoCurrency */}
|
{/* Hero Project: r/CryptoCurrency */}
|
||||||
<Card className="md:col-span-2 md:row-span-2 glass-card group min-h-[300px] rounded-[2.5rem] !bg-slate-950/90">
|
<Link href="https://rcryptocurrency.com" target="_blank" rel="noopener noreferrer" className="md:col-span-2 md:row-span-2">
|
||||||
<div className="absolute inset-0 z-20 bg-gradient-to-t from-black/95 via-black/20 to-transparent p-12 flex flex-col justify-end items-center text-center pointer-events-none pb-20">
|
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] !bg-slate-950/90 cursor-pointer">
|
||||||
<h3 className="text-4xl font-bold text-white shadow-black drop-shadow-xl tracking-tight">r/CryptoCurrency</h3>
|
<div className="absolute inset-0 z-20 bg-gradient-to-t from-black/95 via-black/20 to-transparent p-12 flex flex-col justify-end items-center text-center pointer-events-none pb-20">
|
||||||
<p className="text-gray-200 shadow-black drop-shadow-md max-w-md mt-3 font-medium text-lg leading-relaxed">
|
<h3 className="text-4xl font-bold text-white shadow-black drop-shadow-xl tracking-tight">r/CryptoCurrency</h3>
|
||||||
Governance dashboard for 10M+ members. Real-time DAO metrics.
|
<p className="text-gray-200 shadow-black drop-shadow-md max-w-md mt-3 font-medium text-lg leading-relaxed">
|
||||||
</p>
|
Governance dashboard for 10M+ members. Real-time DAO metrics.
|
||||||
</div>
|
</p>
|
||||||
<div className="absolute inset-x-10 top-10 bottom-48 rounded-2xl overflow-hidden z-10 bg-slate-800/30">
|
</div>
|
||||||
<Image
|
<div className="absolute inset-x-10 top-10 bottom-48 rounded-2xl overflow-hidden z-10 bg-slate-800/30">
|
||||||
src="/images/r-cryptocurrency.png"
|
<Image
|
||||||
alt="r/CryptoCurrency"
|
src="/images/r-cryptocurrency.png"
|
||||||
fill
|
alt="Screenshot of r/CryptoCurrency subreddit homepage showing over 10 million subscribers, community links, and a cartoon moon mascot"
|
||||||
className="object-contain p-8 opacity-80 transition-transform duration-1000 group-hover:scale-105"
|
fill
|
||||||
sizes="(max-width: 768px) 100vw, 50vw"
|
className="object-contain p-8 opacity-80 transition-transform duration-1000 group-hover:scale-105"
|
||||||
/>
|
sizes="(max-width: 768px) 100vw, 50vw"
|
||||||
</div>
|
/>
|
||||||
</Card>
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{/* Secondary: Occupy Wallets */}
|
{/* Secondary: Occupy Wallets */}
|
||||||
<Card className="md:col-span-2 glass-card group min-h-[300px] rounded-[2.5rem]">
|
<Link href="https://occupywallets.art" target="_blank" rel="noopener noreferrer" className="md:col-span-2">
|
||||||
<div className="absolute inset-0 z-20 p-8 flex flex-col justify-center items-center text-center pointer-events-none">
|
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] cursor-pointer">
|
||||||
<h3 className="text-2xl font-bold text-zinc-900 dark:text-white drop-shadow-sm bg-white/60 dark:bg-black/60 backdrop-blur-3xl rounded-xl px-6 py-2">Occupy Wallets</h3>
|
<div className="absolute inset-0 z-20 p-8 flex flex-col justify-center items-center text-center pointer-events-none">
|
||||||
<p className="text-zinc-900 dark:text-zinc-200 font-bold drop-shadow-sm bg-white/60 dark:bg-black/60 backdrop-blur-3xl rounded-xl px-6 py-2 mt-4">High-fidelity gallery for fiscal graffiti.</p>
|
<h3 className="text-2xl font-bold text-zinc-900 dark:text-white drop-shadow-sm bg-white/60 dark:bg-black/60 backdrop-blur-3xl rounded-xl px-6 py-2">Occupy Wallets</h3>
|
||||||
</div>
|
<p className="text-zinc-900 dark:text-zinc-200 font-bold drop-shadow-sm bg-white/60 dark:bg-black/60 backdrop-blur-3xl rounded-xl px-6 py-2 mt-4">High-fidelity gallery for fiscal graffiti.</p>
|
||||||
<div className="absolute inset-4 rounded-xl overflow-hidden z-10 bg-zinc-200/50 dark:bg-zinc-800/50">
|
</div>
|
||||||
<Image
|
<div className="absolute inset-4 rounded-xl overflow-hidden z-10 bg-zinc-200/50 dark:bg-zinc-800/50">
|
||||||
src="/images/occupy-wallets.png"
|
<Image
|
||||||
alt="Occupy Wallets"
|
src="/images/occupy-wallets.png"
|
||||||
fill
|
alt="Screenshot of Occupy Wallets gallery displaying illustrated artwork with political and economic commentary"
|
||||||
className="object-contain p-4 opacity-60 transition-transform duration-1000 group-hover:scale-105"
|
fill
|
||||||
sizes="(max-width: 768px) 100vw, 50vw"
|
className="object-contain p-4 opacity-60 transition-transform duration-1000 group-hover:scale-105"
|
||||||
/>
|
sizes="(max-width: 768px) 100vw, 50vw"
|
||||||
</div>
|
/>
|
||||||
</Card>
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{/* Tertiary: Monerogue & Wowne.ro (Grouped) */}
|
{/* Tertiary: Monerogue & Wowne.ro (Grouped) */}
|
||||||
<Card className="md:col-span-1 glass-card group min-h-[300px] rounded-[2.5rem] !bg-orange-50/95 dark:!bg-orange-950/95 !border-orange-500/20">
|
<Link href="https://play.wowne.ro" target="_blank" rel="noopener noreferrer" className="md:col-span-1">
|
||||||
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
|
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] !bg-orange-50/95 dark:!bg-orange-950/95 !border-orange-500/20 cursor-pointer">
|
||||||
<h3 className="text-xl font-bold text-orange-700 dark:text-orange-400">Privacy Labs</h3>
|
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
|
||||||
<ul className="mt-2 space-y-1 text-sm text-foreground font-semibold">
|
<h3 className="text-xl font-bold text-orange-700 dark:text-orange-400">Privacy Labs</h3>
|
||||||
<li className="flex items-center gap-2">Monerogue <span className="text-xs opacity-60">(Game)</span></li>
|
<ul className="mt-2 space-y-1 text-sm text-foreground font-semibold">
|
||||||
<li className="flex items-center gap-2">Wowne.ro <span className="text-xs opacity-60">(WASM)</span></li>
|
<li className="flex items-center gap-2">Monerogue <span className="text-xs opacity-60">(Game)</span></li>
|
||||||
</ul>
|
<li className="flex items-center gap-2">Wowne.ro <span className="text-xs opacity-60">(WASM)</span></li>
|
||||||
</div>
|
</ul>
|
||||||
<div className="absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border border-orange-500/10 shadow-inner">
|
</div>
|
||||||
<Image
|
<div className="absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border border-orange-500/10 shadow-inner">
|
||||||
src="/images/privacy-labs.png"
|
<Image
|
||||||
alt="Privacy Labs"
|
src="/images/privacy-labs.png"
|
||||||
fill
|
alt="Screenshots of Monerogue roguelike game and Wowne.ro WebAssembly wallet interface"
|
||||||
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
|
fill
|
||||||
sizes="(max-width: 768px) 100vw, 25vw"
|
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
|
||||||
/>
|
sizes="(max-width: 768px) 100vw, 25vw"
|
||||||
</div>
|
/>
|
||||||
</Card>
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{/* Quaternary: MoonPlace */}
|
{/* Quaternary: MoonPlace */}
|
||||||
<Card className="md:col-span-1 glass-card group min-h-[300px] rounded-[2.5rem] !bg-indigo-50/95 dark:!bg-indigo-950/95 !border-indigo-500/20">
|
<Link href="https://moonplace.io" target="_blank" rel="noopener noreferrer" className="md:col-span-1">
|
||||||
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
|
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] !bg-indigo-50/95 dark:!bg-indigo-950/95 !border-indigo-500/20 cursor-pointer">
|
||||||
<h3 className="text-xl font-bold text-indigo-700 dark:text-indigo-400">MoonPlace</h3>
|
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
|
||||||
<p className="text-sm mt-1 text-foreground font-semibold">Collaborative pixel canvas engine.</p>
|
<h3 className="text-xl font-bold text-indigo-700 dark:text-indigo-400">MoonPlace</h3>
|
||||||
</div>
|
<p className="text-sm mt-1 text-foreground font-semibold">Collaborative pixel canvas engine.</p>
|
||||||
<div className="absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border border-indigo-500/10 shadow-inner">
|
</div>
|
||||||
<Image
|
<div className="absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border border-indigo-500/10 shadow-inner">
|
||||||
src="/images/moonplace.png"
|
<Image
|
||||||
alt="MoonPlace"
|
src="/images/moonplace.png"
|
||||||
fill
|
alt="Screenshot of MoonPlace collaborative pixel art canvas showing colorful community-created artwork"
|
||||||
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
|
fill
|
||||||
sizes="(max-width: 768px) 100vw, 25vw"
|
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
|
||||||
/>
|
sizes="(max-width: 768px) 100vw, 25vw"
|
||||||
</div>
|
/>
|
||||||
</Card>
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
import { type ThemeProviderProps } from "next-themes"
|
||||||
|
|
||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||||
|
|||||||
@@ -6,18 +6,24 @@ import { Button } from "@repo/ui/components/button";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export function ThemeToggle() {
|
export function ThemeToggle() {
|
||||||
const { setTheme, resolvedTheme } = useTheme();
|
const { setTheme, resolvedTheme, theme, systemTheme } = useTheme();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[ThemeToggle] mounted:', mounted, 'theme:', theme, 'resolvedTheme:', resolvedTheme);
|
||||||
|
}, [mounted, theme, resolvedTheme]);
|
||||||
|
|
||||||
const toggleTheme = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const toggleTheme = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
const isDark = resolvedTheme === "dark";
|
const isDark = resolvedTheme === "dark";
|
||||||
|
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||||
// Fallback for browsers without startViewTransition
|
|
||||||
if (!document.startViewTransition) {
|
// Skip animation if user prefers reduced motion or browser doesn't support view transitions
|
||||||
|
if (prefersReducedMotion || !document.startViewTransition) {
|
||||||
setTheme(isDark ? "light" : "dark");
|
setTheme(isDark ? "light" : "dark");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -51,18 +57,21 @@ export function ThemeToggle() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isDark = resolvedTheme === "dark";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={mounted ? toggleTheme : undefined}
|
onClick={mounted ? toggleTheme : undefined}
|
||||||
className="rounded-full"
|
className="rounded-full"
|
||||||
aria-label="Toggle theme"
|
aria-label={mounted ? (isDark ? "Switch to light mode" : "Switch to dark mode") : "Toggle theme"}
|
||||||
>
|
>
|
||||||
{mounted && resolvedTheme === "dark" ? (
|
{/* Show the icon representing what clicking will switch TO */}
|
||||||
<Moon className="h-5 w-5" />
|
{mounted && isDark ? (
|
||||||
) : (
|
|
||||||
<Sun className="h-5 w-5" />
|
<Sun className="h-5 w-5" />
|
||||||
|
) : (
|
||||||
|
<Moon className="h-5 w-5" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const DataGrid = () => {
|
|||||||
width="2" height="2" fill="currentColor"
|
width="2" height="2" fill="currentColor"
|
||||||
className="text-blue-500 dark:text-blue-400"
|
className="text-blue-500 dark:text-blue-400"
|
||||||
>
|
>
|
||||||
<motion.animateMotion
|
<animateMotion
|
||||||
path="M 0 50 H 100"
|
path="M 0 50 H 100"
|
||||||
dur="5s"
|
dur="5s"
|
||||||
repeatCount="indefinite"
|
repeatCount="indefinite"
|
||||||
|
|||||||
@@ -77,14 +77,9 @@ const StreamLine = ({ d, themeMode, onHit, width, index }: StreamLineProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: NebulaProps) => {
|
export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: NebulaProps) => {
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
const monitorControls = useAnimation();
|
const monitorControls = useAnimation();
|
||||||
const [monitorColor, setMonitorColor] = useState(themeMode === 'light' ? "text-slate-400" : "text-emerald-500/60");
|
const [monitorColor, setMonitorColor] = useState(themeMode === 'light' ? "text-slate-400" : "text-emerald-500/60");
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Sync color with theme if no impacts have happened yet
|
// Sync color with theme if no impacts have happened yet
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -130,25 +125,23 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu
|
|||||||
}, [density, themeMode, targetX, targetY]);
|
}, [density, themeMode, targetX, targetY]);
|
||||||
|
|
||||||
const handleHit = useCallback(() => {
|
const handleHit = useCallback(() => {
|
||||||
const colors = themeMode === 'light'
|
const colors = themeMode === 'light'
|
||||||
? ["text-slate-400", "text-blue-400", "text-indigo-400", "text-emerald-400"]
|
? ["text-slate-400", "text-blue-400", "text-indigo-400", "text-emerald-400"]
|
||||||
: ["text-emerald-500/50", "text-blue-500/50", "text-purple-500/50", "text-cyan-500/50", "text-teal-500/50"];
|
: ["text-emerald-500/50", "text-blue-500/50", "text-purple-500/50", "text-cyan-500/50", "text-teal-500/50"];
|
||||||
|
|
||||||
setMonitorColor(colors[Math.floor(Math.random() * colors.length)]);
|
setMonitorColor(colors[Math.floor(Math.random() * colors.length)]);
|
||||||
|
|
||||||
monitorControls.start({
|
monitorControls.start({
|
||||||
scale: [1, 1.1, 1], // Slightly more pronounced pulse
|
scale: [1, 1.1, 1],
|
||||||
filter: [
|
filter: [
|
||||||
"brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))",
|
"brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))",
|
||||||
"brightness(1.3) drop-shadow(0 0 12px currentColor)", // Stronger glow
|
"brightness(1.3) drop-shadow(0 0 12px currentColor)",
|
||||||
"brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))"
|
"brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))"
|
||||||
],
|
],
|
||||||
transition: { duration: 1.5, ease: "easeOut" } // Smoother, longer pulse
|
transition: { duration: 1.5, ease: "easeOut" }
|
||||||
});
|
});
|
||||||
}, [monitorControls, themeMode]);
|
}, [monitorControls, themeMode]);
|
||||||
|
|
||||||
if (!mounted) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("absolute inset-0 overflow-hidden pointer-events-none z-0", className)}>
|
<div className={cn("absolute inset-0 overflow-hidden pointer-events-none z-0", className)}>
|
||||||
|
|
||||||
|
|||||||
Generated
+579
-97
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user