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:*",
|
||||
"framer-motion": "^11.0.0",
|
||||
"lucide-react": "^0.350.0",
|
||||
"next": "14.1.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"next": "^15.1.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^5.5.0",
|
||||
@@ -27,7 +27,7 @@
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"eslint-config-next": "^15.1.3",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"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",
|
||||
"theme_color": "#000000",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/images/favicon.svg",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/images/favicon_192.png",
|
||||
"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 { SiteLogo } from "@/components/layout/site-logo";
|
||||
import Link from "next/link";
|
||||
import { Header } from "@/components/layout/header";
|
||||
import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-icons/fa";
|
||||
import { FaXTwitter, FaTelegram, FaReddit } from "react-icons/fa6";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -19,19 +17,9 @@ export const metadata: Metadata = {
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<main className="relative flex flex-col items-center overflow-hidden">
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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="text-center">
|
||||
<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>
|
||||
{/* Social links are in the footer, but we can highlight them here or leave for footer */}
|
||||
<p className="text-sm text-muted-foreground mb-4">Find us on your favorite platforms</p>
|
||||
<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>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
overflow: hidden;
|
||||
transition: all 300ms;
|
||||
}
|
||||
|
||||
|
||||
.section-container {
|
||||
@apply mask-backdrop rounded-[2.5rem] p-12 md:p-20 text-card-foreground;
|
||||
}
|
||||
@@ -95,8 +95,20 @@
|
||||
.glass-card {
|
||||
@apply mask-backdrop rounded-[2rem] p-10 text-card-foreground;
|
||||
}
|
||||
|
||||
|
||||
.glass-card:hover {
|
||||
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.",
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: "/images/favicon.svg", type: "image/svg+xml" },
|
||||
{ url: "/images/favicon_192.png", sizes: "192x192", type: "image/png" },
|
||||
],
|
||||
shortcut: "/images/favicon_192.png",
|
||||
shortcut: "/images/favicon.svg",
|
||||
apple: [
|
||||
{ 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 { ContactForm } from "@/components/contact/contact-form";
|
||||
import { FounderSignature } from "@/components/about/founder-signature";
|
||||
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 { Button } from "@repo/ui/components/button";
|
||||
import { Card } from "@repo/ui/components/card";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="relative flex flex-col items-center justify-between overflow-hidden">
|
||||
|
||||
<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="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">
|
||||
<Header />
|
||||
|
||||
<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">
|
||||
<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
|
||||
</h1>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DataGrid } from "@repo/ui/procedural/data-grid";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
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 { Card } from "@repo/ui/components/card";
|
||||
import type { Metadata } from "next";
|
||||
@@ -19,22 +18,10 @@ export const metadata: Metadata = {
|
||||
export default function ServicesPage() {
|
||||
return (
|
||||
<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 />
|
||||
<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 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">
|
||||
<div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4">
|
||||
{/* Context Header */}
|
||||
<div className="mb-20">
|
||||
<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">
|
||||
<h3 className="text-xl font-bold mb-3">Zero Capital Upfront</h3>
|
||||
<p className="text-base text-muted-foreground leading-relaxed">
|
||||
Our "Toast" style partnership means we win when you win. Low friction onboarding
|
||||
with performance-based incentives.
|
||||
Our revenue-share partnership means no setup fees or monthly subscriptions—we
|
||||
only earn when you're making sales.
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="glass-card hover:border-indigo-500/30">
|
||||
|
||||
@@ -7,12 +7,13 @@ export const FounderSignature = () => {
|
||||
{/* Left Column: Bio */}
|
||||
<div className="space-y-8 text-left section-container">
|
||||
<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">
|
||||
<Image
|
||||
src="/images/logo_w_circle_trans.jpg"
|
||||
alt="Such Software Icon"
|
||||
fill
|
||||
className="object-cover scale-[1.02]"
|
||||
<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
|
||||
src="/images/favicon.svg"
|
||||
alt="Such Software Icon"
|
||||
width={72}
|
||||
height={72}
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -23,13 +24,13 @@ export const FounderSignature = () => {
|
||||
|
||||
<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">
|
||||
"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 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 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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,18 +62,18 @@ export const FounderSignature = () => {
|
||||
<span className="text-emerald-500 text-[10px]">03</span>
|
||||
Core Competencies
|
||||
</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">
|
||||
<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 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 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 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>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
'use client';
|
||||
import { useFormState, useFormStatus } from 'react-dom';
|
||||
import { useActionState } from 'react';
|
||||
import { useFormStatus } from 'react-dom';
|
||||
import { submitContactForm } from '@/actions/submit-contact';
|
||||
import { TurnstileWidget } from './turnstile-widget';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Button } from '@repo/ui/components/button';
|
||||
|
||||
function SubmitButton() {
|
||||
const { pending } = useFormStatus();
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
size="lg"
|
||||
className="w-full md:w-64 rounded-xl text-lg font-semibold"
|
||||
>
|
||||
@@ -20,12 +21,22 @@ function SubmitButton() {
|
||||
}
|
||||
|
||||
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 formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
// Reset form on successful submission
|
||||
useEffect(() => {
|
||||
if (state.success && formRef.current) {
|
||||
formRef.current.reset();
|
||||
setToken('');
|
||||
}
|
||||
}, [state.success]);
|
||||
|
||||
return (
|
||||
<form
|
||||
action={formAction}
|
||||
<form
|
||||
ref={formRef}
|
||||
action={formAction}
|
||||
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" />
|
||||
@@ -41,40 +52,46 @@ export function ContactForm() {
|
||||
<div className="grid md:grid-cols-2 gap-8 relative z-10">
|
||||
<div className="space-y-3">
|
||||
<label htmlFor="name" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Full Name</label>
|
||||
<input
|
||||
name="name"
|
||||
id="name"
|
||||
<input
|
||||
name="name"
|
||||
id="name"
|
||||
placeholder="Jamie Doe"
|
||||
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"
|
||||
required
|
||||
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 className="space-y-3">
|
||||
<label htmlFor="email" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Work Email</label>
|
||||
<input
|
||||
name="email"
|
||||
id="email"
|
||||
type="email"
|
||||
<input
|
||||
name="email"
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="jamie@example.com"
|
||||
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"
|
||||
required
|
||||
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 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>
|
||||
<textarea
|
||||
name="message"
|
||||
id="message"
|
||||
<textarea
|
||||
name="message"
|
||||
id="message"
|
||||
placeholder="What are you building? Mention any specific constraints, timelines, or technologies..."
|
||||
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"
|
||||
required
|
||||
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>
|
||||
|
||||
<input type="hidden" name="token" value={token} />
|
||||
@@ -92,13 +109,17 @@ export function ContactForm() {
|
||||
</div>
|
||||
|
||||
{state?.message && (
|
||||
<div 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
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
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">
|
||||
<span className="text-xl">{state.success ? "✓" : "⚠"}</span>
|
||||
<span className="text-xl" aria-hidden="true">{state.success ? "✓" : "⚠"}</span>
|
||||
<p className="font-medium">{state.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,19 +5,31 @@ import { NebulaField } from "@repo/ui/procedural/nebula-field";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function DynamicBackground() {
|
||||
const { theme } = useTheme();
|
||||
const { resolvedTheme, theme, systemTheme } = useTheme();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
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 (
|
||||
<NebulaField
|
||||
className="fixed inset-0 z-0 h-screen w-screen"
|
||||
themeMode={theme === 'dark' ? 'dark' : 'light'}
|
||||
<NebulaField
|
||||
className="fixed inset-0 z-0 h-screen w-screen"
|
||||
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() {
|
||||
return (
|
||||
<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) */}
|
||||
<Image
|
||||
src="/images/logo_dark_w_name_trans.png"
|
||||
src="/images/logo_dark_w_name.svg"
|
||||
alt="Such Software"
|
||||
fill
|
||||
className="object-contain hidden dark:block"
|
||||
@@ -17,7 +17,7 @@ export function SiteLogo() {
|
||||
/>
|
||||
{/* Light mode logo (black text) */}
|
||||
<Image
|
||||
src="/images/logo_light_w_name_trans.png"
|
||||
src="/images/logo_light_w_name.svg"
|
||||
alt="Such Software"
|
||||
fill
|
||||
className="object-contain block dark:hidden"
|
||||
|
||||
@@ -1,81 +1,90 @@
|
||||
import { Card } from "@repo/ui/components/card";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export const PortfolioGrid = () => {
|
||||
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">
|
||||
{/* 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">
|
||||
<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">
|
||||
<h3 className="text-4xl font-bold text-white shadow-black drop-shadow-xl tracking-tight">r/CryptoCurrency</h3>
|
||||
<p className="text-gray-200 shadow-black drop-shadow-md max-w-md mt-3 font-medium text-lg leading-relaxed">
|
||||
Governance dashboard for 10M+ members. Real-time DAO metrics.
|
||||
</p>
|
||||
</div>
|
||||
<div className="absolute inset-x-10 top-10 bottom-48 rounded-2xl overflow-hidden z-10 bg-slate-800/30">
|
||||
<Image
|
||||
src="/images/r-cryptocurrency.png"
|
||||
alt="r/CryptoCurrency"
|
||||
fill
|
||||
className="object-contain p-8 opacity-80 transition-transform duration-1000 group-hover:scale-105"
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Link href="https://rcryptocurrency.com" target="_blank" rel="noopener noreferrer" className="md:col-span-2 md:row-span-2">
|
||||
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] !bg-slate-950/90 cursor-pointer">
|
||||
<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">
|
||||
<h3 className="text-4xl font-bold text-white shadow-black drop-shadow-xl tracking-tight">r/CryptoCurrency</h3>
|
||||
<p className="text-gray-200 shadow-black drop-shadow-md max-w-md mt-3 font-medium text-lg leading-relaxed">
|
||||
Governance dashboard for 10M+ members. Real-time DAO metrics.
|
||||
</p>
|
||||
</div>
|
||||
<div className="absolute inset-x-10 top-10 bottom-48 rounded-2xl overflow-hidden z-10 bg-slate-800/30">
|
||||
<Image
|
||||
src="/images/r-cryptocurrency.png"
|
||||
alt="Screenshot of r/CryptoCurrency subreddit homepage showing over 10 million subscribers, community links, and a cartoon moon mascot"
|
||||
fill
|
||||
className="object-contain p-8 opacity-80 transition-transform duration-1000 group-hover:scale-105"
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* Secondary: Occupy Wallets */}
|
||||
<Card className="md:col-span-2 glass-card group min-h-[300px] rounded-[2.5rem]">
|
||||
<div className="absolute inset-0 z-20 p-8 flex flex-col justify-center items-center text-center pointer-events-none">
|
||||
<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>
|
||||
<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>
|
||||
<div className="absolute inset-4 rounded-xl overflow-hidden z-10 bg-zinc-200/50 dark:bg-zinc-800/50">
|
||||
<Image
|
||||
src="/images/occupy-wallets.png"
|
||||
alt="Occupy Wallets"
|
||||
fill
|
||||
className="object-contain p-4 opacity-60 transition-transform duration-1000 group-hover:scale-105"
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Link href="https://occupywallets.art" target="_blank" rel="noopener noreferrer" className="md:col-span-2">
|
||||
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] cursor-pointer">
|
||||
<div className="absolute inset-0 z-20 p-8 flex flex-col justify-center items-center text-center pointer-events-none">
|
||||
<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>
|
||||
<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>
|
||||
<div className="absolute inset-4 rounded-xl overflow-hidden z-10 bg-zinc-200/50 dark:bg-zinc-800/50">
|
||||
<Image
|
||||
src="/images/occupy-wallets.png"
|
||||
alt="Screenshot of Occupy Wallets gallery displaying illustrated artwork with political and economic commentary"
|
||||
fill
|
||||
className="object-contain p-4 opacity-60 transition-transform duration-1000 group-hover:scale-105"
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 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">
|
||||
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
|
||||
<h3 className="text-xl font-bold text-orange-700 dark:text-orange-400">Privacy Labs</h3>
|
||||
<ul className="mt-2 space-y-1 text-sm text-foreground font-semibold">
|
||||
<li className="flex items-center gap-2">Monerogue <span className="text-xs opacity-60">(Game)</span></li>
|
||||
<li className="flex items-center gap-2">Wowne.ro <span className="text-xs opacity-60">(WASM)</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<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">
|
||||
<Image
|
||||
src="/images/privacy-labs.png"
|
||||
alt="Privacy Labs"
|
||||
fill
|
||||
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
|
||||
sizes="(max-width: 768px) 100vw, 25vw"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Link href="https://play.wowne.ro" target="_blank" rel="noopener noreferrer" className="md:col-span-1">
|
||||
<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">
|
||||
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
|
||||
<h3 className="text-xl font-bold text-orange-700 dark:text-orange-400">Privacy Labs</h3>
|
||||
<ul className="mt-2 space-y-1 text-sm text-foreground font-semibold">
|
||||
<li className="flex items-center gap-2">Monerogue <span className="text-xs opacity-60">(Game)</span></li>
|
||||
<li className="flex items-center gap-2">Wowne.ro <span className="text-xs opacity-60">(WASM)</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<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">
|
||||
<Image
|
||||
src="/images/privacy-labs.png"
|
||||
alt="Screenshots of Monerogue roguelike game and Wowne.ro WebAssembly wallet interface"
|
||||
fill
|
||||
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
|
||||
sizes="(max-width: 768px) 100vw, 25vw"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 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">
|
||||
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
|
||||
<h3 className="text-xl font-bold text-indigo-700 dark:text-indigo-400">MoonPlace</h3>
|
||||
<p className="text-sm mt-1 text-foreground font-semibold">Collaborative pixel canvas engine.</p>
|
||||
</div>
|
||||
<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">
|
||||
<Image
|
||||
src="/images/moonplace.png"
|
||||
alt="MoonPlace"
|
||||
fill
|
||||
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
|
||||
sizes="(max-width: 768px) 100vw, 25vw"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Link href="https://moonplace.io" target="_blank" rel="noopener noreferrer" className="md:col-span-1">
|
||||
<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">
|
||||
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
|
||||
<h3 className="text-xl font-bold text-indigo-700 dark:text-indigo-400">MoonPlace</h3>
|
||||
<p className="text-sm mt-1 text-foreground font-semibold">Collaborative pixel canvas engine.</p>
|
||||
</div>
|
||||
<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">
|
||||
<Image
|
||||
src="/images/moonplace.png"
|
||||
alt="Screenshot of MoonPlace collaborative pixel art canvas showing colorful community-created artwork"
|
||||
fill
|
||||
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
|
||||
sizes="(max-width: 768px) 100vw, 25vw"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from "react"
|
||||
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) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
|
||||
@@ -6,18 +6,24 @@ import { Button } from "@repo/ui/components/button";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { setTheme, resolvedTheme } = useTheme();
|
||||
const { setTheme, resolvedTheme, theme, systemTheme } = useTheme();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// Debug
|
||||
useEffect(() => {
|
||||
console.log('[ThemeToggle] mounted:', mounted, 'theme:', theme, 'resolvedTheme:', resolvedTheme);
|
||||
}, [mounted, theme, resolvedTheme]);
|
||||
|
||||
const toggleTheme = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const isDark = resolvedTheme === "dark";
|
||||
|
||||
// Fallback for browsers without startViewTransition
|
||||
if (!document.startViewTransition) {
|
||||
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
// Skip animation if user prefers reduced motion or browser doesn't support view transitions
|
||||
if (prefersReducedMotion || !document.startViewTransition) {
|
||||
setTheme(isDark ? "light" : "dark");
|
||||
return;
|
||||
}
|
||||
@@ -51,18 +57,21 @@ export function ThemeToggle() {
|
||||
});
|
||||
};
|
||||
|
||||
const isDark = resolvedTheme === "dark";
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={mounted ? toggleTheme : undefined}
|
||||
className="rounded-full"
|
||||
aria-label="Toggle theme"
|
||||
aria-label={mounted ? (isDark ? "Switch to light mode" : "Switch to dark mode") : "Toggle theme"}
|
||||
>
|
||||
{mounted && resolvedTheme === "dark" ? (
|
||||
<Moon className="h-5 w-5" />
|
||||
) : (
|
||||
{/* Show the icon representing what clicking will switch TO */}
|
||||
{mounted && isDark ? (
|
||||
<Sun className="h-5 w-5" />
|
||||
) : (
|
||||
<Moon className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ export const DataGrid = () => {
|
||||
width="2" height="2" fill="currentColor"
|
||||
className="text-blue-500 dark:text-blue-400"
|
||||
>
|
||||
<motion.animateMotion
|
||||
<animateMotion
|
||||
path="M 0 50 H 100"
|
||||
dur="5s"
|
||||
repeatCount="indefinite"
|
||||
|
||||
@@ -77,14 +77,9 @@ const StreamLine = ({ d, themeMode, onHit, width, index }: StreamLineProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: NebulaProps) => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: NebulaProps) => {
|
||||
const monitorControls = useAnimation();
|
||||
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
|
||||
useEffect(() => {
|
||||
@@ -130,25 +125,23 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu
|
||||
}, [density, themeMode, targetX, targetY]);
|
||||
|
||||
const handleHit = useCallback(() => {
|
||||
const colors = themeMode === 'light'
|
||||
const colors = themeMode === 'light'
|
||||
? ["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"];
|
||||
|
||||
|
||||
setMonitorColor(colors[Math.floor(Math.random() * colors.length)]);
|
||||
|
||||
monitorControls.start({
|
||||
scale: [1, 1.1, 1], // Slightly more pronounced pulse
|
||||
scale: [1, 1.1, 1],
|
||||
filter: [
|
||||
"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) drop-shadow(0 0 0px rgba(0,0,0,0))",
|
||||
"brightness(1.3) drop-shadow(0 0 12px currentColor)",
|
||||
"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]);
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<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