Add blog and products pages (including bauhaus privacy policy)

This commit is contained in:
jwinterm
2026-02-06 09:45:33 -05:00
parent 8a119b4149
commit 1705497ca6
19 changed files with 1241 additions and 4 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="166.89827mm"
height="166.89827mm"
viewBox="0 0 166.89827 166.89827"
version="1.1"
id="svg5"
xml:space="preserve"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="logo.svg"
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.2938725"
inkscape:cx="227.6113"
inkscape:cy="291.37338"
inkscape:window-width="2124"
inkscape:window-height="1236"
inkscape:window-x="5342"
inkscape:window-y="533"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" /><defs
id="defs2" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-23.473152,-57.772301)"><circle
style="fill:#000000;fill-opacity:1;stroke-width:3.46156"
id="path464-3"
cx="106.92229"
cy="141.22144"
r="83.449135" /><circle
style="fill:#fbeb0a;fill-opacity:1;stroke-width:3.166"
id="path464"
cx="106.92229"
cy="141.22144"
r="76.324005" /><path
style="fill:#000000;stroke-width:0.0801176"
d="m 139.63844,138.52915 c -0.18147,-0.56182 -0.41552,-1.25584 -0.52013,-1.54226 -0.10459,-0.28642 -0.38003,-1.11564 -0.61209,-1.8427 -0.23206,-0.72707 -0.52022,-1.59234 -0.64038,-1.92283 -0.12015,-0.33048 -0.45598,-1.32194 -0.74629,-2.20323 -0.29031,-0.88129 -0.56604,-1.71051 -0.61272,-1.8427 -0.0467,-0.1322 -0.82073,-2.45448 -1.7201,-5.16063 -0.89936,-2.70616 -1.68189,-4.9499 -1.73896,-4.9861 -0.12028,-0.0763 -0.43047,-0.0859 -0.67292,-0.0208 -0.15096,0.0405 -0.17372,0.12213 -0.21755,0.78005 -0.12999,1.95135 -1.16082,6.43648 -1.88269,8.19163 -0.0893,0.21708 -0.25836,0.64706 -0.37571,0.95551 -0.11736,0.30846 -0.27338,0.69969 -0.34671,0.86941 -0.24008,0.55563 -1.38521,2.81312 -1.4894,2.93618 -0.056,0.0661 -0.12246,0.17892 -0.14777,0.25072 -0.159,0.45108 -1.83035,2.57492 -2.66228,3.38305 -1.13574,1.10326 -2.95918,2.37677 -3.25967,2.27661 -0.11612,-0.0387 -0.65285,-1.03872 -1.75318,-3.2665 -0.4897,-0.99146 -1.05487,-2.12712 -1.25593,-2.5237 -0.4063,-0.80139 -1.44492,-3.07154 -1.99769,-4.36641 -2.12761,-4.98407 -3.71452,-9.83686 -4.38833,-13.4197 -0.5346,-2.84257 -0.56554,-3.03886 -0.68186,-4.32635 -0.0478,-0.52877 -0.12211,-1.33996 -0.16518,-1.80264 -0.14845,-1.59452 -0.0617,-4.95675 0.17048,-6.6097 0.12285,-0.87452 0.26944,-1.71745 0.43357,-2.493167 l 0.13763,-0.650457 0.5315,0.19086 c 2.704,0.970974 4.84499,3.105274 6.49821,6.477934 0.66632,1.35931 0.94742,2.05845 1.41735,3.52518 0.19766,0.6169 0.42534,1.31993 0.50595,1.56229 0.177,0.53213 0.79149,2.92591 0.89464,3.48511 0.0406,0.22033 0.10492,0.4727 0.14285,0.56083 0.0643,0.14936 0.21635,0.83704 0.26575,1.20176 0.0175,0.12947 0.0908,0.16424 0.38152,0.18111 0.19789,0.0115 0.40539,-0.009 0.46111,-0.0455 0.0557,-0.0365 0.29903,-0.85491 0.5407,-1.81868 0.49033,-1.95544 1.03233,-3.79169 1.32414,-4.48597 0.037,-0.0881 0.15611,-0.43064 0.26459,-0.76112 0.19688,-0.59976 0.41281,-1.16183 0.9695,-2.5237 0.36534,-0.89377 1.40085,-2.8867 1.82278,-3.50811 1.74156,-2.56498 3.63874,-3.550567 5.40155,-2.80612 1.07838,0.45541 2.00696,1.20238 2.93534,2.36127 0.77486,0.96725 1.84124,2.98552 2.55423,4.83425 0.085,0.22032 0.18459,0.47269 0.22137,0.56082 0.22986,0.55076 0.40878,1.06166 0.40878,1.16726 0,0.0674 0.0327,0.15688 0.0727,0.19875 0.04,0.0419 0.10928,0.20229 0.15403,0.35652 0.0447,0.15423 0.17754,0.53278 0.29512,0.84123 0.11756,0.30846 0.24473,0.65096 0.2826,0.76112 0.0379,0.11016 0.16333,0.45267 0.27881,0.76112 0.11548,0.30845 0.28283,0.76813 0.3719,1.0215 0.0891,0.25337 0.18732,0.46067 0.21837,0.46067 0.0506,0 0.31359,-0.86359 0.48793,-1.60235 0.37468,-1.5877 1.69482,-5.90643 2.40003,-7.85152 1.38751,-3.82699 2.96992,-6.633145 4.97716,-8.826183 1.10226,-1.204296 3.03615,-2.505246 4.30093,-2.893281 0.33264,-0.10205 0.58043,-0.222715 0.58086,-0.28285 8e-4,-0.05768 0.0339,-0.0844 0.0744,-0.05938 0.1854,0.114582 0.61715,2.537171 0.85742,4.811072 0.19122,1.809671 0.19073,6.048662 -8.8e-4,7.971682 -0.26849,2.69413 -0.6402,5.14294 -1.08841,7.17052 -0.23503,1.06322 -0.86495,3.63767 -1.00958,4.12606 -0.0587,0.19829 -0.17857,0.6129 -0.26632,0.92135 -0.28685,1.00822 -1.11822,3.66271 -1.48044,4.72693 -0.43753,1.28553 -2.0887,5.72616 -2.29384,6.16906 -0.0408,0.0881 -0.1431,0.3405 -0.22728,0.56082 -0.25022,0.6549 -0.73009,1.81666 -0.91005,2.20323 -0.96227,2.0671 -1.82445,3.68757 -2.26683,4.26052 -0.0881,0.11414 -0.21431,0.29004 -0.28041,0.39089 -0.39047,0.5958 -1.31347,1.59812 -2.06154,2.23871 -0.37181,0.31838 -1.31735,0.92149 -1.9844,1.26572 -0.35655,0.184 -1.26039,0.49686 -1.43541,0.49686 -0.0739,0 -0.22673,-0.35266 -0.44276,-1.0215 z"
id="path2693" /><path
style="fill:#000000;stroke-width:0.0801176"
d="m 76.362086,140.72257 c -2.615616,-0.29816 -3.873305,-0.56623 -6.048877,-1.28929 -1.646445,-0.5472 -2.543126,-0.93632 -3.875176,-1.68165 -0.899666,-0.5034 -2.338885,-1.44147 -2.814642,-1.83456 -2.379519,-1.96608 -3.084078,-2.72615 -4.380384,-4.7255 -0.638284,-0.98446 -1.643316,-2.87073 -2.110215,-3.84864 -0.933137,-1.95445 -4.340287,-6.31504 0.485635,-5.92094 2.04413,0.16693 1.517052,0.12759 3.156698,0.22334 2.244706,-0.0788 3.992187,0.91358 6.253267,2.7338 1.213632,0.977 1.491992,1.20566 1.96288,1.61243 0.264388,0.22839 0.697023,0.57415 0.961411,0.76836 0.264388,0.1942 0.498732,0.3737 0.520764,0.39889 0.02203,0.0252 0.23835,0.17484 0.480706,0.33256 2.053261,1.33625 4.268491,2.08661 6.93017,2.34744 0.600304,0.0588 2.354396,-0.0782 3.204703,-0.25034 2.270835,-0.45973 4.026498,-1.58656 4.736595,-3.04009 0.301292,-0.61673 0.310518,-0.66076 0.301194,-1.43739 -0.02662,-2.21724 -1.204901,-3.7297 -3.891557,-4.99528 -1.376336,-0.64834 -2.597207,-1.01198 -5.392463,-1.60616 -2.036164,-0.43283 -2.312474,-0.49561 -3.566718,-0.81037 -3.326179,-0.83475 -5.554161,-1.98948 -7.451543,-3.86204 -2.158216,-2.12997 -3.375167,-4.85943 -3.500575,-7.85133 -0.08649,-2.06338 0.259899,-3.66162 1.211756,-5.59109 0.592777,-1.201594 1.290735,-2.168428 2.307877,-3.196952 1.920787,-1.942287 3.709745,-3.068399 6.312324,-3.973472 3.802167,-1.322242 7.953978,-1.514308 11.857402,-0.548531 1.563584,0.386858 2.545808,0.73631 3.897174,1.38652 2.770677,1.33311 3.399483,2.036069 4.761006,4.302927 0.608477,1.013074 1.516662,2.370778 2.397724,4.858228 0.303849,0.85784 1.477724,1.68453 -0.457264,1.73605 -0.75812,0.0202 -1.673422,0.22675 -2.900817,0.22183 -1.241008,-0.005 -1.715873,0.31585 -3.427921,-0.46752 -4.99551,-2.28577 -6.192535,-2.63035 -8.796545,-2.53224 -1.423156,0.0536 -2.503211,0.28896 -3.526225,0.76836 -2.144323,1.00486 -2.954941,2.26974 -2.241933,3.49828 0.355897,0.61323 1.283816,1.33174 2.320603,1.79692 0.701526,0.31475 2.288337,0.82035 3.487614,1.11125 4.024428,0.97617 4.726477,1.18165 6.6097,1.93461 0.973022,0.38904 2.743395,1.29681 3.705983,1.90027 1.749318,1.09668 3.690228,2.84034 4.729338,4.24871 0.410424,0.55627 1.298965,2.01733 1.298965,2.13594 0,0.0197 0.10468,0.2637 0.232623,0.54218 1.374716,2.99218 1.537792,6.69399 0.440541,10.00026 -0.257297,0.77529 -1.065418,2.5318 -1.267646,2.75531 -0.04758,0.0526 -0.122571,0.15367 -0.166635,0.22461 -0.705187,1.13525 -2.024293,2.61979 -2.964351,3.33612 -0.264388,0.20147 -0.498732,0.38724 -0.520764,0.41283 -0.119745,0.13907 -1.191263,0.84332 -1.927933,1.26711 -2.41247,1.38787 -4.840386,2.1433 -8.16449,2.54033 -0.901762,0.1077 -4.415947,0.15387 -5.169976,0.0679 z"
id="path2691"
sodipodi:nodetypes="ssssssscsssssssssssssssssssssssssssssssssssssssssss" /><path
style="fill:#000000;stroke-width:0.0801176"
d="m 88.405315,200.33212 c -1.471719,-0.16888 -2.890039,-0.47625 -4.439709,-0.96214 -1.436665,-0.45046 -1.658717,-0.53847 -3.692552,-1.4635 -1.016563,-0.46235 -3.021704,-1.5905 -3.605291,-2.02843 -0.110161,-0.0827 -0.542796,-0.38971 -0.96141,-0.68231 -0.418615,-0.29261 -0.779144,-0.55405 -0.801176,-0.581 -0.02203,-0.0269 -0.435159,-0.37199 -0.91806,-0.76678 -3.12595,-2.55556 -5.646948,-5.69263 -7.562446,-9.41055 -1.468577,-2.85045 -2.299609,-5.36078 -2.771612,-8.37228 -0.296415,-1.89122 -0.374782,-2.32473 -0.428354,-2.36962 -0.03121,-0.0262 -0.525441,-0.21964 -1.098282,-0.42997 -4.326343,-1.58859 -7.871428,-3.25237 -9.885532,-4.63951 -0.847505,-0.58369 -1.78606,-1.67091 -2.223405,-2.5756 -0.402353,-0.8323 -0.509702,-1.31169 -0.509276,-2.27426 7.21e-4,-1.64987 0.399918,-2.8517 1.25338,-3.77354 0.577407,-0.62367 1.199612,-0.93452 2.093105,-1.04571 1.294426,-0.16107 2.524438,0.13019 4.58485,1.0857 4.171785,1.93465 6.371597,2.84317 6.884199,2.84317 0.190785,0 0.230262,-0.0681 0.522601,-0.90133 0.355481,-1.0132 1.431488,-3.19604 1.964784,-3.98585 1.822206,-2.69867 3.951253,-4.59855 6.651931,-5.93594 1.680324,-0.83209 3.501494,-1.39619 5.64829,-1.7495 4.208915,-0.69271 8.392363,-0.17545 10.655637,1.31751 1.411308,0.93095 1.956534,2.07787 1.860442,3.91353 -0.07491,1.43097 -0.48363,2.47923 -1.354267,3.47335 -0.627353,0.71632 -1.033435,1.02542 -1.9512,1.48521 -1.309632,0.6561 -2.5792,1.02351 -4.64391,1.34391 -3.172549,0.49233 -5.835136,1.77439 -7.072924,3.4057 -0.655637,0.86408 -0.864939,1.2407 -0.734355,1.3214 0.203786,0.12595 5.40042,1.35431 7.407514,1.75096 4.546001,0.89841 9.984705,1.61913 15.141399,2.0065 2.909474,0.21855 5.580534,0.34608 5.654014,0.26993 0.034,-0.0353 0.24072,-0.46074 0.45926,-0.94545 0.21853,-0.48471 0.65398,-1.44808 0.96766,-2.14083 0.31368,-0.69274 0.57032,-1.27844 0.57032,-1.30155 0,-0.0231 0.40652,-0.86238 0.90338,-1.86505 1.85788,-3.74927 3.51968,-6.12846 6.06335,-8.68086 3.10251,-3.11314 6.30719,-4.98745 10.85944,-6.35131 3.52915,-1.05734 7.46731,-1.33623 11.29657,-0.8 2.83472,0.39696 5.67468,1.42622 8.07631,2.92702 2.55288,1.5953 4.99251,4.10341 6.77817,6.96843 0.50263,0.80644 1.59472,2.93972 2.05465,4.01354 0.1274,0.29744 0.27622,0.54079 0.33071,0.54079 0.0545,0 0.45207,-0.12835 0.8835,-0.28523 2.09931,-0.76337 2.90423,-1.02354 5.19088,-1.67782 1.8761,-0.53682 3.08856,-0.75971 4.17871,-0.76821 1.07482,-0.008 1.54234,0.1543 2.04623,0.71205 0.42306,0.46828 0.50562,0.78823 0.50035,1.9391 -0.01,2.15526 -0.74458,4.18297 -2.15232,5.94014 -1.90973,2.38378 -4.66309,4.20959 -8.18969,5.43079 l -1.2132,0.42011 -0.056,0.53391 c -0.3006,2.86623 -1.48189,6.71769 -2.90367,9.46703 -1.50049,2.90154 -2.99966,4.93028 -5.38757,7.2907 -2.05543,2.03177 -4.87445,3.98956 -7.51261,5.21746 -0.47403,0.22063 -0.92068,0.43249 -0.99256,0.4708 -0.39932,0.21287 -2.06901,0.85495 -3.20737,1.23341 -1.91633,0.63708 -2.38479,0.72789 -3.75234,0.72731 -1.14027,-8e-4 -1.17329,-0.005 -1.78973,-0.26994 -1.10787,-0.47534 -1.79642,-1.35794 -2.19398,-2.81234 -0.21058,-0.77036 -0.18904,-2.62151 0.0402,-3.45911 0.53361,-1.94939 1.69535,-3.63399 3.22691,-4.67927 0.32801,-0.22386 1.08309,-0.63482 1.67797,-0.91323 4.61373,-2.15938 6.9188,-3.70991 8.69758,-5.85055 0.68108,-0.81964 0.986,-1.28067 1.67084,-2.52631 0.48196,-0.8766 0.67664,-1.35691 0.58653,-1.44702 -0.02,-0.02 -0.48311,0.0714 -1.02911,0.20319 -2.13452,0.51514 -5.18079,1.0846 -9.00449,1.68328 -1.95791,0.30655 -4.76036,0.6357 -9.21352,1.08213 -1.9531,0.1958 -5.49322,0.44698 -7.15662,0.50778 l -1.54838,0.0566 -0.22483,0.56082 c -0.96857,2.41617 -2.77389,5.85283 -4.16658,7.93165 -1.75329,2.61706 -2.83544,3.91065 -4.60958,5.51023 -2.25585,2.03388 -4.43824,3.33407 -7.010316,4.1765 -2.363388,0.77407 -4.427496,1.09361 -6.970229,1.07905 -0.815197,-0.005 -1.914664,-0.0581 -2.44326,-0.11878 z m 3.004083,-12.69748 c 2.506435,-0.49476 4.428857,-1.6116 6.170774,-3.58493 0.478041,-0.54156 2.001219,-2.74533 2.001219,-2.89543 0,-0.0358 -0.297437,-0.0895 -0.66097,-0.11928 -5.179302,-0.42436 -11.18766,-1.15305 -14.280959,-1.73199 -4.256347,-0.79661 -5.336953,-1.02543 -7.759033,-1.64294 -0.786359,-0.20048 -1.450771,-0.34348 -1.47647,-0.31779 -0.143595,0.1436 0.99229,2.71193 1.705417,3.85609 1.124463,1.80413 2.786384,3.38634 4.725971,4.4993 1.733615,0.99477 4.440217,1.83615 6.496752,2.0196 0.874899,0.078 2.481942,0.0349 3.077299,-0.0826 z m 28.401712,-17.50011 c 4.39872,-0.29548 10.40014,-1.08245 14.6693,-1.92361 1.44118,-0.28396 6.61362,-1.46449 6.67189,-1.52275 0.0547,-0.0547 -0.46273,-0.99395 -0.85902,-1.55909 -0.52721,-0.75186 -1.5131,-1.68642 -2.40634,-2.28107 -1.11403,-0.74165 -2.10949,-1.15602 -3.69473,-1.53797 -0.71606,-0.17253 -1.09156,-0.20878 -2.5237,-0.24363 -1.458,-0.0355 -1.81074,-0.0189 -2.64388,0.12392 -1.67543,0.28729 -2.73637,0.63653 -4.12551,1.35804 -2.03882,1.05894 -3.55515,2.4302 -4.89173,4.42369 -0.52105,0.77714 -1.5276,2.68343 -1.64368,3.11291 -0.0496,0.18361 -0.0402,0.18763 0.33764,0.14345 0.21379,-0.025 0.71318,-0.0673 1.10976,-0.0939 z"
id="path302" /></g></svg>

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 46 KiB

+3 -1
View File
@@ -1,5 +1,6 @@
import { FounderSignature } from "@/components/about/founder-signature";
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-icons/fa";
import { FaXTwitter, FaTelegram, FaDiscord } from "react-icons/fa6";
import type { Metadata } from "next";
@@ -22,7 +23,7 @@ export default function AboutPage() {
<main className="relative flex flex-col items-center overflow-hidden">
<Header />
<div id="main-content" 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 pb-24 md:pb-20">
<h1 className="text-4xl md:text-5xl font-bold mb-10 text-center">About Us</h1>
<div className="section-container">
@@ -95,6 +96,7 @@ export default function AboutPage() {
</div>
</div>
<MobileNav />
</main>
);
}
@@ -0,0 +1,71 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Bauhaus Echo is Live | Such Software Blog",
description: "Our first mobile game is available on Android and iOS. A visual memory puzzle inspired by Bauhaus design.",
alternates: { canonical: "/blog/bauhaus-echo-launch" },
};
export default function BauhausEchoLaunchPost() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<article id="main-content" className="z-10 w-full max-w-3xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/blog" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Blog
</Link>
<header className="mb-12">
<time className="text-sm text-muted-foreground">February 6, 2026</time>
<h1 className="text-3xl md:text-4xl font-bold mt-2">Bauhaus Echo is Live</h1>
</header>
<div className="prose dark:prose-invert max-w-none">
<p>
Bauhaus Echo is now available on Google Play and the App Store. It is a visual memory game
where you memorize tile arrangements, watch them shuffle, and reconstruct the original pattern.
</p>
<h2>The Concept</h2>
<p>
The game shows you a grid of colorful geometric tiles inspired by Bauhaus design. You get a
few seconds to memorize their positions. Then the tiles spin, shrink, and scatter to the
bottom of the screen. Your job is to drag them back to where they belong.
</p>
<h2>Four Game Modes</h2>
<ul>
<li><strong>Classic</strong> - The core experience. Memorize and reconstruct.</li>
<li><strong>Speed</strong> - Tiles appear one at a time. Place each before the next drops.</li>
<li><strong>Endless</strong> - Speed mode that loops forever. See how long you last.</li>
<li><strong>Memory</strong> - Traditional card-matching pairs.</li>
</ul>
<h2>Tech Stack</h2>
<p>
Built with libGDX, a cross-platform Java game framework. The same codebase runs on Android,
iOS (via RoboVM), desktop, and web (via GWT). The tile assets are packed into a TextureAtlas
for efficient rendering.
</p>
<h2>Download</h2>
<p>
Get it on{" "}
<a href="https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho" target="_blank" rel="noopener noreferrer">
Google Play
</a>{" "}
or the{" "}
<a href="https://apps.apple.com/app/bauhaus-echo" target="_blank" rel="noopener noreferrer">
App Store
</a>
. Free with ads, or pay once to remove them.
</p>
</div>
</article>
<MobileNav />
</main>
);
}
+69
View File
@@ -0,0 +1,69 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Blog | Such Software",
description: "News, updates, and technical articles from Such Software.",
alternates: { canonical: "/blog" },
};
// For now, hardcoded posts. Can migrate to MDX/CMS later.
const posts = [
{
slug: "smirk-wallet-release",
title: "Smirk Wallet v0.1.6",
date: "2026-02-06",
excerpt: "Non-custodial browser extension for social tipping with BTC, LTC, XMR, WOW, and GRIN. Now on GitHub, Chrome Web Store pending.",
draft: false,
},
{
slug: "website-relaunch-2026",
title: "Website Relaunch",
date: "2026-02-06",
excerpt: "We rebuilt such.software from the ground up. Here is what we used and why.",
draft: false,
},
{
slug: "bauhaus-echo-launch",
title: "Bauhaus Echo is Live",
date: "2026-02-06",
excerpt: "Our first mobile game is available on Android and iOS. A visual memory puzzle inspired by Bauhaus design.",
draft: true,
},
];
const publishedPosts = posts.filter((p) => !p.draft);
export default function BlogPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<h1 className="text-4xl md:text-5xl font-bold mb-6">Blog</h1>
<p className="text-xl text-muted-foreground mb-12">
News, launches, and technical notes.
</p>
{publishedPosts.length === 0 ? (
<p className="text-muted-foreground">No posts yet. Check back soon.</p>
) : (
<div className="space-y-6">
{publishedPosts.map((post) => (
<Link key={post.slug} href={`/blog/${post.slug}`}>
<Card className="glass-card hover:border-primary/30 transition-colors cursor-pointer">
<time className="text-sm text-muted-foreground">{post.date}</time>
<h2 className="text-2xl font-bold mt-1 mb-2">{post.title}</h2>
<p className="text-muted-foreground">{post.excerpt}</p>
</Card>
</Link>
))}
</div>
)}
</div>
<MobileNav />
</main>
);
}
@@ -0,0 +1,114 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Smirk Wallet v0.1.6 | Such Software Blog",
description: "Announcing Smirk Wallet, a non-custodial browser extension for social tipping with BTC, LTC, XMR, WOW, and GRIN.",
alternates: { canonical: "/blog/smirk-wallet-release" },
};
export default function SmirkWalletReleasePost() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<article id="main-content" className="z-10 w-full max-w-3xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/blog" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Blog
</Link>
<header className="mb-12">
<time className="text-sm text-muted-foreground">February 6, 2026</time>
<h1 className="text-3xl md:text-4xl font-bold mt-2">Smirk Wallet v0.1.6</h1>
</header>
<div className="prose dark:prose-invert max-w-none">
<p>
Smirk Wallet is a non-custodial browser extension that lets you tip people cryptocurrency
by their Telegram or Discord username. We have shipped six releases and the extension is
available now on GitHub.
</p>
<h2>What It Does</h2>
<p>
One 12-word seed phrase gives you addresses for five currencies: Bitcoin, Litecoin, Monero,
Wownero, and Grin. Link your Telegram or Discord account on smirk.cash, and anyone running
Smirk can send you crypto by username.
</p>
<p>
Tips are encrypted with the recipient's public key. Only they can claim. If a tip goes
unclaimed, the sender can claw it back. No funds get lost.
</p>
<h2>Non-Custodial by Design</h2>
<p>
Your private spend keys are generated locally and never leave your browser. They are
encrypted with your password. We store only public keys and view keys (for Monero/Wownero
balance scanning). We cannot move your funds.
</p>
<h2>What We Built</h2>
<ul>
<li>Browser extension (TypeScript, Preact, Vite)</li>
<li>Backend API (Rust, Axum, PostgreSQL)</li>
<li>Light wallet servers for XMR and WOW</li>
<li>Grin wallet with client-side WASM</li>
<li>Telegram and Discord bots for tip notifications</li>
<li>User dashboard at smirk.cash</li>
</ul>
<h2>Chrome Web Store Status</h2>
<p>
Our initial submission was rejected for requesting an unused permission (activeTab).
We had included it for a planned feature that was not yet implemented. The fix is already
in v0.1.6. We have removed the permission and resubmitted.
</p>
<p>
In the meantime, you can install manually from{" "}
<a href="https://github.com/Such-Software/smirk-extension/releases" target="_blank" rel="noopener noreferrer">
GitHub Releases
</a>
. Firefox support is also available.
</p>
<h2>Open Source</h2>
<p>
Everything is open source. The extension, backend, and WASM libraries are all on GitHub:
</p>
<ul>
<li>
<a href="https://github.com/Such-Software/smirk-extension" target="_blank" rel="noopener noreferrer">
smirk-extension
</a>
</li>
<li>
<a href="https://github.com/Such-Software/monero-oxide" target="_blank" rel="noopener noreferrer">
monero-oxide
</a>{" "}
(Monero WASM)
</li>
</ul>
<h2>Get Started</h2>
<p>
Visit{" "}
<a href="https://smirk.cash" target="_blank" rel="noopener noreferrer">
smirk.cash
</a>{" "}
to learn more, or join the community on{" "}
<a href="https://t.me/smirkwallet" target="_blank" rel="noopener noreferrer">
Telegram
</a>{" "}
or{" "}
<a href="https://discord.gg/7EnsaWTm6C" target="_blank" rel="noopener noreferrer">
Discord
</a>
.
</p>
</div>
</article>
<MobileNav />
</main>
);
}
@@ -0,0 +1,76 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Website Relaunch | Such Software Blog",
description: "We rebuilt such.software from the ground up. Here is what we used and why.",
alternates: { canonical: "/blog/website-relaunch-2026" },
};
export default function WebsiteRelaunchPost() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<article id="main-content" className="z-10 w-full max-w-3xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/blog" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Blog
</Link>
<header className="mb-12">
<time className="text-sm text-muted-foreground">February 6, 2026</time>
<h1 className="text-3xl md:text-4xl font-bold mt-2">Website Relaunch</h1>
</header>
<div className="prose dark:prose-invert max-w-none">
<p>
We rebuilt such.software. New design, new infrastructure, same focus on performance and accessibility.
</p>
<h2>Stack</h2>
<ul>
<li><strong>Framework:</strong> Next.js 15 (App Router)</li>
<li><strong>Language:</strong> TypeScript</li>
<li><strong>Styling:</strong> Tailwind CSS with CSS variables for theming</li>
<li><strong>Animation:</strong> Framer Motion</li>
<li><strong>Monorepo:</strong> Turborepo + pnpm workspaces</li>
<li><strong>Testing:</strong> Playwright with axe-core for accessibility</li>
<li><strong>Bot Protection:</strong> Cloudflare Turnstile</li>
<li><strong>Email:</strong> Resend</li>
</ul>
<h2>Procedural Graphics</h2>
<p>
The homepage features two custom procedural animations: NebulaField and DataGrid. Both are
SVG-based, theme-aware, and respect prefers-reduced-motion. NebulaField renders animated
data streams flowing toward a central icon using quadratic Bezier curves. DataGrid draws
a pulsing grid with traveling data packets.
</p>
<h2>Accessibility</h2>
<p>
We run 42 automated accessibility tests across Chromium, Firefox, and WebKit on every build.
Tests cover WCAG 2.2 AA compliance, heading hierarchy, color contrast, keyboard navigation,
and form accessibility. Lighthouse scores are 99-100 across all pages.
</p>
<h2>Performance</h2>
<p>
The NebulaField component is lazy loaded with next/dynamic and requestIdleCallback. Canvas
rendering is throttled. Fonts are loaded via next/font for zero layout shift. Images use
next/image for automatic optimization.
</p>
<h2>Structure</h2>
<p>
The site now has Products, Services, Blog, and About sections. Products showcases our
standalone offerings: Webshops, Custom Websites, Smirk Wallet, and Bauhaus Echo. Services
covers our partnership model and consulting work. Mobile users get a bottom navigation bar.
</p>
</div>
</article>
<MobileNav />
</main>
);
}
+3 -1
View File
@@ -5,6 +5,7 @@ import { ContactForm } from "@/components/contact/contact-form";
import { FounderSignature } from "@/components/about/founder-signature";
import { ServicesPreview } from "@/components/landing/services-preview";
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { DynamicBackground } from "@/components/landing/dynamic-background";
import Link from "next/link";
import { Button } from "@repo/ui/components/button";
@@ -103,7 +104,7 @@ export default function Home() {
<FounderSignature />
</div>
<div id="contact" className="w-full mt-20 mb-20 flex flex-col items-center">
<div id="contact" className="w-full mt-20 mb-20 pb-16 md:pb-0 flex flex-col items-center">
<div className="w-full max-w-6xl text-left mb-12 section-container border-l-4 border-l-cyan-500">
<h2 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Start a Project</h2>
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
@@ -114,6 +115,7 @@ export default function Home() {
<ContactForm />
</div>
</div>
<MobileNav />
</main>
);
}
@@ -0,0 +1,97 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Bauhaus Echo | Such Software",
description: "Visual memory puzzle game inspired by Bauhaus design. Memorize, shuffle, reconstruct. Android, iOS, Web, Desktop.",
alternates: { canonical: "/products/bauhaus-echo" },
};
const gameModes = [
{ name: "Classic", description: "Memorize tile positions, reconstruct after shuffle" },
{ name: "Speed", description: "Tiles drop one at a time, place each before the next appears" },
{ name: "Endless", description: "Speed mode with tile cycling, play forever" },
{ name: "Memory", description: "Traditional card-matching pairs game" },
];
export default function BauhausEchoPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Products
</Link>
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-blue-600 dark:text-blue-400">
Bauhaus Echo
</h1>
<div className="prose dark:prose-invert max-w-none mb-12">
<p className="text-xl text-muted-foreground leading-relaxed">
A visual memory puzzle game inspired by Bauhaus design aesthetics.
Memorize arrangements of geometric tiles, watch them shuffle, then reconstruct the pattern.
</p>
</div>
<div className="flex flex-wrap gap-4 mb-12">
<a href="https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho" target="_blank" rel="noopener noreferrer">
<Button size="lg">Google Play</Button>
</a>
<a href="https://apps.apple.com/app/bauhaus-echo" target="_blank" rel="noopener noreferrer">
<Button variant="outline" size="lg">App Store</Button>
</a>
</div>
<h2 className="text-2xl font-bold mb-6">How It Works</h2>
<div className="section-container border-l-4 border-blue-500 mb-12">
<ol className="space-y-4 text-muted-foreground">
<li><strong>1. Memorization</strong> — Tiles appear in a grid. Study their positions.</li>
<li><strong>2. Shuffle</strong> — Tiles spin, shrink, and scatter to the bottom.</li>
<li><strong>3. Reconstruction</strong> — Drag tiles back to their original positions.</li>
<li><strong>4. Scoring</strong> — Earn points based on accuracy and speed.</li>
</ol>
</div>
<h2 className="text-2xl font-bold mb-6">Game Modes</h2>
<div className="grid md:grid-cols-2 gap-6 mb-12">
{gameModes.map((mode) => (
<Card key={mode.name} className="glass-card border-blue-500/20">
<h3 className="text-xl font-bold mb-2">{mode.name}</h3>
<p className="text-muted-foreground">{mode.description}</p>
</Card>
))}
</div>
<h2 className="text-2xl font-bold mb-6">Features</h2>
<div className="grid md:grid-cols-3 gap-6 mb-12">
<Card className="glass-card border-blue-500/20">
<h3 className="font-bold mb-2">500+ Tile Designs</h3>
<p className="text-sm text-muted-foreground">Curated Bauhaus-style geometric art</p>
</Card>
<Card className="glass-card border-blue-500/20">
<h3 className="font-bold mb-2">Multiple Grid Sizes</h3>
<p className="text-sm text-muted-foreground">2×2, 3×3, 4×4, 5×5</p>
</Card>
<Card className="glass-card border-blue-500/20">
<h3 className="font-bold mb-2">Cross-Platform</h3>
<p className="text-sm text-muted-foreground">Android, iOS, Web, Desktop</p>
</Card>
</div>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<Link href="/products/bauhaus-echo/privacy" className="hover:text-foreground">
Privacy Policy
</Link>
<span>•</span>
<span>Contact: apps@such.software</span>
</div>
</div>
<MobileNav />
</main>
);
}
@@ -0,0 +1,147 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Privacy Policy | Bauhaus Echo | Such Software",
description: "Privacy policy for Bauhaus Echo mobile game.",
alternates: { canonical: "/products/bauhaus-echo/privacy" },
};
export default function BauhausEchoPrivacyPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-3xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products/bauhaus-echo" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Bauhaus Echo
</Link>
<h1 className="text-3xl md:text-4xl font-bold mb-4">Privacy Policy for Bauhaus Echo</h1>
<p className="text-muted-foreground mb-8">Last Updated: January 31, 2026</p>
<div className="prose dark:prose-invert max-w-none">
<p>
<strong>Such Software LLC</strong> (&quot;we,&quot; &quot;us,&quot; or &quot;our&quot;) operates the Bauhaus Echo mobile
application (the &quot;App&quot;). This page informs you of our policies regarding the collection,
use, and disclosure of personal data when you use our App.
</p>
<h2>Information We Collect</h2>
<h3>Advertising Data</h3>
<p>
We use <strong>Google AdMob</strong> to display advertisements. AdMob may collect and use
data to show you personalized ads. This data may include:
</p>
<ul>
<li>Device identifiers (Advertising ID)</li>
<li>IP address</li>
<li>General location (country/region)</li>
<li>App usage data</li>
</ul>
<p>
For more information on how Google uses data, visit:{" "}
<a href="https://policies.google.com/technologies/partner-sites" target="_blank" rel="noopener noreferrer">
How Google uses information from sites or apps that use our services
</a>
</p>
<p>
<strong>iOS Users:</strong> We will ask for your permission via App Tracking Transparency (ATT)
before collecting data for personalized advertising. You can deny this request and still use
the app with non-personalized ads.
</p>
<h3>In-App Purchases</h3>
<p>
If you purchase the &quot;Remove Ads&quot; upgrade, your transaction is processed by Google Play (Android)
or the Apple App Store (iOS). We do not have access to your payment information. We only receive
confirmation of your purchase status.
</p>
<h3>High Scores &amp; Leaderboards</h3>
<p>If you submit scores to our online leaderboards, we collect:</p>
<ul>
<li>Your chosen display name (anonymous by default)</li>
<li>Your game scores and statistics</li>
<li>A randomly generated anonymous player ID</li>
</ul>
<p>
We do <strong>not</strong> require or collect your real name, email, or any other personal
information to use leaderboards.
</p>
<h3>Locally Stored Data</h3>
<p>The App stores the following data locally on your device:</p>
<ul>
<li>Game progress and high scores</li>
<li>Settings preferences (sound, music, grid size)</li>
<li>Premium purchase status</li>
</ul>
<p>This data is not transmitted to our servers unless you use the online leaderboard feature.</p>
<h2>How We Use Your Information</h2>
<p>We use collected data to:</p>
<ul>
<li>Display advertisements (unless you purchase &quot;Remove Ads&quot;)</li>
<li>Process in-app purchases</li>
<li>Maintain online leaderboards</li>
<li>Improve the App experience</li>
</ul>
<h2>Third-Party Services</h2>
<p>The App uses the following third-party services that may collect information:</p>
<ul>
<li><strong>Google AdMob</strong> — Advertising</li>
<li><strong>Google Play Services</strong> — Android platform and billing</li>
<li><strong>Apple StoreKit</strong> — iOS platform and billing</li>
</ul>
<h2>Children&apos;s Privacy</h2>
<p>
The App is intended for general audiences and does not knowingly collect personal information
from children under 13. If you believe we have collected data from a child under 13, please
contact us immediately.
</p>
<h2>Data Retention</h2>
<ul>
<li><strong>Advertising data</strong> is managed by Google per their retention policies</li>
<li><strong>Leaderboard data</strong> is retained indefinitely but can be deleted upon request</li>
<li><strong>Local data</strong> is stored on your device until you uninstall the App</li>
</ul>
<h2>Your Rights</h2>
<p>You may:</p>
<ul>
<li>
<strong>Opt out of personalized ads</strong> via your device settings (Android: Settings → Google → Ads;
iOS: Settings → Privacy → Tracking)
</li>
<li><strong>Request deletion</strong> of your leaderboard data by contacting us</li>
<li><strong>Remove local data</strong> by uninstalling the App</li>
</ul>
<h2>Changes to This Policy</h2>
<p>
We may update this Privacy Policy from time to time. We will notify you of any changes by
posting the new Privacy Policy on this page and updating the &quot;Last Updated&quot; date.
</p>
<h2>Contact Us</h2>
<p>If you have any questions about this Privacy Policy, please contact us:</p>
<p>
<strong>Such Software LLC</strong><br />
Email: apps@such.software<br />
Website: https://such.software
</p>
<h2>Consent</h2>
<p>By using Bauhaus Echo, you consent to this Privacy Policy.</p>
</div>
</div>
<MobileNav />
</main>
);
}
@@ -0,0 +1,92 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Custom Websites | Such Software",
description: "Real-time platforms and interactive web experiences. See our work on rcryptocurrency.com and moonplace.io.",
alternates: { canonical: "/products/custom-websites" },
};
export default function CustomWebsitesPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Products
</Link>
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-purple-600 dark:text-purple-400">
Custom Websites
</h1>
<div className="prose dark:prose-invert max-w-none mb-12">
<p className="text-xl text-muted-foreground leading-relaxed">
Real-time platforms, collaborative tools, and high-end brand experiences.
We build performant web applications that handle scale.
</p>
</div>
<h2 className="text-2xl font-bold mb-6">Featured Projects</h2>
<div className="grid md:grid-cols-2 gap-6 mb-12">
<a href="https://rcryptocurrency.com" target="_blank" rel="noopener noreferrer">
<Card className="glass-card border-purple-500/20 hover:border-purple-500/50 transition-colors cursor-pointer h-full">
<h3 className="text-xl font-bold mb-3">rcryptocurrency.com</h3>
<p className="text-muted-foreground mb-4">
Governance dashboard for the r/CryptoCurrency subreddit with 10M+ members.
Real-time DAO metrics, proposal tracking, and community analytics.
</p>
<ul className="text-sm text-muted-foreground space-y-1">
<li>Real-time data via WebSocket</li>
<li>Reddit API integration</li>
<li>On-chain governance tracking</li>
</ul>
</Card>
</a>
<a href="https://moonplace.io" target="_blank" rel="noopener noreferrer">
<Card className="glass-card border-purple-500/20 hover:border-purple-500/50 transition-colors cursor-pointer h-full">
<h3 className="text-xl font-bold mb-3">moonplace.io</h3>
<p className="text-muted-foreground mb-4">
Collaborative pixel art canvas with real-time multiplayer.
Thousands of concurrent users painting together.
</p>
<ul className="text-sm text-muted-foreground space-y-1">
<li>WebSocket real-time sync</li>
<li>Canvas rendering optimization</li>
<li>User authentication and cooldowns</li>
</ul>
</Card>
</a>
</div>
<div className="grid md:grid-cols-2 gap-6 mb-12">
<Card className="glass-card border-purple-500/20">
<h3 className="text-xl font-bold mb-3">Real-time Platforms</h3>
<p className="text-muted-foreground">
Socket.io and WebRTC integration for collaborative tools, dashboards,
and live data visualization.
</p>
</Card>
<Card className="glass-card border-purple-500/20">
<h3 className="text-xl font-bold mb-3">Brand Motion</h3>
<p className="text-muted-foreground">
Framer Motion and Three.js powered interfaces that tell your brand's
story through motion and depth.
</p>
</Card>
</div>
<div className="text-center">
<Link href="/about">
<Button size="lg">Start a Project</Button>
</Link>
</div>
</div>
<MobileNav />
</main>
);
}
+91
View File
@@ -0,0 +1,91 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Products | Such Software",
description: "Tools and applications built by Such Software. Webshops, custom websites, Smirk Wallet, and Bauhaus Echo.",
alternates: { canonical: "/products" },
openGraph: {
title: "Products | Such Software",
description: "Tools and applications built by Such Software.",
type: "website",
},
};
const products = [
{
slug: "webshops",
title: "Webshops",
description: "Headless Medusa commerce with revenue-share pricing. No upfront costs.",
color: "emerald",
},
{
slug: "custom-websites",
title: "Custom Websites",
description: "Real-time platforms and interactive experiences. See rcryptocurrency.com and moonplace.io.",
color: "purple",
},
{
slug: "smirk-wallet",
title: "Smirk Wallet",
description: "Non-custodial browser wallet for social tipping. BTC, LTC, XMR, WOW, GRIN.",
color: "amber",
},
{
slug: "bauhaus-echo",
title: "Bauhaus Echo",
description: "Visual memory puzzle game inspired by Bauhaus design. Android, iOS, Web.",
color: "blue",
},
];
const colorClasses: Record<string, string> = {
emerald: "border-emerald-500/30 hover:border-emerald-500/60",
purple: "border-purple-500/30 hover:border-purple-500/60",
amber: "border-amber-500/30 hover:border-amber-500/60",
blue: "border-blue-500/30 hover:border-blue-500/60",
};
const titleColors: Record<string, string> = {
emerald: "text-emerald-600 dark:text-emerald-400",
purple: "text-purple-600 dark:text-purple-400",
amber: "text-amber-600 dark:text-amber-400",
blue: "text-blue-600 dark:text-blue-400",
};
export default function ProductsPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4 pb-24 md:pb-20">
<div className="text-center mb-16">
<h1 className="text-4xl md:text-6xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-amber-400 to-purple-500">
Our Products
</h1>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
Tools we have built, from commerce infrastructure to consumer apps.
</p>
</div>
<div className="grid md:grid-cols-2 gap-8">
{products.map((product) => (
<Link key={product.slug} href={`/products/${product.slug}`}>
<Card className={`glass-card h-full min-h-[200px] ${colorClasses[product.color]} transition-all cursor-pointer`}>
<h2 className={`text-2xl font-bold mb-3 ${titleColors[product.color]}`}>
{product.title}
</h2>
<p className="text-muted-foreground leading-relaxed">
{product.description}
</p>
</Card>
</Link>
))}
</div>
</div>
<MobileNav />
</main>
);
}
@@ -0,0 +1,129 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Smirk Wallet | Such Software",
description: "Non-custodial browser wallet for social tipping. Send BTC, LTC, XMR, WOW, and GRIN by Telegram or Discord username.",
alternates: { canonical: "/products/smirk-wallet" },
};
const currencies = [
{ name: "Bitcoin", symbol: "BTC", type: "secp256k1" },
{ name: "Litecoin", symbol: "LTC", type: "secp256k1" },
{ name: "Monero", symbol: "XMR", type: "ed25519" },
{ name: "Wownero", symbol: "WOW", type: "ed25519" },
{ name: "Grin", symbol: "GRIN", type: "secp256k1" },
];
export default function SmirkWalletPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Products
</Link>
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-amber-600 dark:text-amber-400">
Smirk Wallet
</h1>
<div className="prose dark:prose-invert max-w-none mb-12">
<p className="text-xl text-muted-foreground leading-relaxed">
Non-custodial multi-currency wallet for social tipping.
Tip anyone on Telegram or Discord by username. Your keys never leave your browser.
</p>
</div>
<div className="flex flex-wrap gap-4 mb-12">
<a href="https://smirk.cash" target="_blank" rel="noopener noreferrer">
<Button size="lg">Visit smirk.cash</Button>
</a>
<a href="https://github.com/Such-Software/smirk-extension/releases" target="_blank" rel="noopener noreferrer">
<Button variant="outline" size="lg">Download Extension</Button>
</a>
</div>
<h2 className="text-2xl font-bold mb-6">Features</h2>
<div className="grid md:grid-cols-2 gap-6 mb-12">
<Card className="glass-card border-amber-500/20">
<h3 className="text-xl font-bold mb-3">Non-Custodial</h3>
<p className="text-muted-foreground">
Your private keys are generated locally and encrypted with your password.
We never see your spend keys. Your funds are always yours.
</p>
</Card>
<Card className="glass-card border-amber-500/20">
<h3 className="text-xl font-bold mb-3">Social Tipping</h3>
<p className="text-muted-foreground">
Link your Telegram or Discord account. Anyone with Smirk can send you
crypto by username. No wallet address needed.
</p>
</Card>
<Card className="glass-card border-amber-500/20">
<h3 className="text-xl font-bold mb-3">Encrypted Tips</h3>
<p className="text-muted-foreground">
Tips are encrypted with the recipient's public key. Only they can claim.
Senders can reclaim unclaimed tips (no lost funds).
</p>
</Card>
<Card className="glass-card border-amber-500/20">
<h3 className="text-xl font-bold mb-3">One Seed, Five Chains</h3>
<p className="text-muted-foreground">
A single 12-word seed phrase derives addresses for all supported currencies.
Restore your entire wallet with one backup.
</p>
</Card>
</div>
<h2 className="text-2xl font-bold mb-6">Supported Currencies</h2>
<div className="overflow-x-auto mb-12">
<table className="w-full text-left">
<thead>
<tr className="border-b border-border">
<th className="py-3 px-4 font-semibold">Currency</th>
<th className="py-3 px-4 font-semibold">Symbol</th>
<th className="py-3 px-4 font-semibold">Key Type</th>
</tr>
</thead>
<tbody>
{currencies.map((c) => (
<tr key={c.symbol} className="border-b border-border/50">
<td className="py-3 px-4">{c.name}</td>
<td className="py-3 px-4 font-mono">{c.symbol}</td>
<td className="py-3 px-4 text-muted-foreground">{c.type}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="section-container border-l-4 border-amber-500 mb-12">
<h2 className="text-2xl font-bold mb-4">Why These Coins?</h2>
<p className="text-muted-foreground leading-relaxed">
BTC is the most widely used. LTC is fast and cheap (MWEB support coming).
XMR is the gold standard for privacy. WOW has a great community.
GRIN is lightweight, private, and has the fairest emission schedule of any coin.
</p>
</div>
<div className="flex gap-4">
<a href="https://t.me/smirkwallet" target="_blank" rel="noopener noreferrer">
<Button variant="outline">Telegram</Button>
</a>
<a href="https://discord.gg/7EnsaWTm6C" target="_blank" rel="noopener noreferrer">
<Button variant="outline">Discord</Button>
</a>
<a href="https://github.com/Such-Software/smirk-extension" target="_blank" rel="noopener noreferrer">
<Button variant="outline">GitHub</Button>
</a>
</div>
</div>
<MobileNav />
</main>
);
}
@@ -0,0 +1,84 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Webshops | Such Software",
description: "Headless Medusa commerce engines with revenue-share pricing. No upfront costs, aligned incentives.",
alternates: { canonical: "/products/webshops" },
};
export default function WebshopsPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Products
</Link>
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-emerald-600 dark:text-emerald-400">
Webshops
</h1>
<div className="prose dark:prose-invert max-w-none mb-12">
<p className="text-xl text-muted-foreground leading-relaxed">
We build headless commerce platforms on Medusa v2 with a revenue-share model.
No massive setup fees, no monthly retainers. We only win when you make sales.
</p>
</div>
<div className="grid md:grid-cols-2 gap-6 mb-12">
<Card className="glass-card border-emerald-500/20">
<h3 className="text-xl font-bold mb-3">Zero Upfront Cost</h3>
<p className="text-muted-foreground">
We invest our development time and infrastructure. You focus on your brand and products.
We take a small percentage of sales once you are live.
</p>
</Card>
<Card className="glass-card border-emerald-500/20">
<h3 className="text-xl font-bold mb-3">Own Your Data</h3>
<p className="text-muted-foreground">
Unlike Shopify, you own your customer data, product catalog, and order history.
No platform lock-in, ever.
</p>
</Card>
<Card className="glass-card border-emerald-500/20">
<h3 className="text-xl font-bold mb-3">Flexible Architecture</h3>
<p className="text-muted-foreground">
Subscriptions, bundles, B2B pricing, multi-warehouse inventory.
Model your exact business logic without platform constraints.
</p>
</Card>
<Card className="glass-card border-emerald-500/20">
<h3 className="text-xl font-bold mb-3">Performance First</h3>
<p className="text-muted-foreground">
Next.js storefronts optimized for Core Web Vitals. 100/100 Lighthouse scores,
fast load times, high conversion rates.
</p>
</Card>
</div>
<div className="section-container border-l-4 border-emerald-500 mb-12">
<h2 className="text-2xl font-bold mb-4">Tech Stack</h2>
<ul className="space-y-2 text-muted-foreground">
<li><strong>Backend:</strong> Medusa v2 (Node.js, PostgreSQL)</li>
<li><strong>Frontend:</strong> Next.js 15, React 18, Tailwind CSS</li>
<li><strong>Payments:</strong> Stripe, PayPal, crypto (optional)</li>
<li><strong>Hosting:</strong> Vercel, Railway, or your infrastructure</li>
</ul>
</div>
<div className="text-center">
<Link href="/about">
<Button size="lg">Get in Touch</Button>
</Link>
</div>
</div>
<MobileNav />
</main>
);
}
+2 -1
View File
@@ -1,6 +1,7 @@
import { DataGrid } from "@repo/ui/procedural/data-grid";
import { Button } from "@repo/ui/components/button";
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import Link from "next/link";
import { Card } from "@repo/ui/components/card";
import type { Metadata } from "next";
@@ -150,7 +151,7 @@ export default function ServicesPage() {
</Link>
</div>
</div>
<MobileNav />
</main>
);
}
+12
View File
@@ -20,12 +20,24 @@ export function Header() {
<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="/products"
className={pathname.startsWith("/products") ? "text-primary" : "hover:text-primary transition-colors"}
>
Products
</Link>
<Link
href="/services"
className={pathname === "/services" ? "text-primary" : "hover:text-primary transition-colors"}
>
Services
</Link>
<Link
href="/blog"
className={pathname.startsWith("/blog") ? "text-primary" : "hover:text-primary transition-colors"}
>
Blog
</Link>
<Link
href="/about"
className={pathname === "/about" ? "text-primary" : "hover:text-primary transition-colors"}
@@ -0,0 +1,51 @@
'use client';
import Link from "next/link";
import { usePathname } from "next/navigation";
import { FaHouse, FaBoxesStacked, FaNewspaper, FaCircleInfo, FaGear } from "react-icons/fa6";
const navItems = [
{ href: "/", label: "Home", icon: FaHouse },
{ href: "/products", label: "Products", icon: FaBoxesStacked },
{ href: "/services", label: "Services", icon: FaGear },
{ href: "/blog", label: "Blog", icon: FaNewspaper },
{ href: "/about", label: "About", icon: FaCircleInfo },
];
export function MobileNav() {
const pathname = usePathname();
const isActive = (href: string) => {
if (href === "/") return pathname === "/";
return pathname.startsWith(href);
};
return (
<nav
className="fixed bottom-0 left-0 right-0 z-50 md:hidden bg-background/95 backdrop-blur-xl border-t border-border/50"
aria-label="Mobile navigation"
style={{ paddingBottom: "env(safe-area-inset-bottom)" }}
>
<div className="flex justify-around items-center h-16 px-2">
{navItems.map((item) => {
const Icon = item.icon;
const active = isActive(item.href);
return (
<Link
key={item.href}
href={item.href}
className={`flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-lg transition-colors ${
active
? "text-primary"
: "text-muted-foreground hover:text-foreground"
}`}
>
<Icon className="h-5 w-5" aria-hidden="true" />
<span className="text-[10px] font-medium">{item.label}</span>
</Link>
);
})}
</div>
</nav>
);
}