mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-22 06:18:32 +02:00
add play and app store buttons and review vegan blog
This commit is contained in:
@@ -4,11 +4,9 @@ date: "2026-03-04"
|
|||||||
excerpt: "How we built a Bauhaus-inspired memory puzzle with libGDX, shipped it to five platforms, and added global leaderboards with HMAC-signed score submissions"
|
excerpt: "How we built a Bauhaus-inspired memory puzzle with libGDX, shipped it to five platforms, and added global leaderboards with HMAC-signed score submissions"
|
||||||
---
|
---
|
||||||
|
|
||||||
Bauhaus Echo is now available on the [App Store](https://apps.apple.com/us/app/bauhaus-echo/id6759010657), [Google Play](https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho), [itch.io](https://suchsoftware.itch.io/bauhaus-echo), and [the web](https://bauhaus.such.software). It is a visual memory game where you memorize tile arrangements, watch them shuffle, and reconstruct the original pattern.
|
<BauhausEchoStoreButtons />
|
||||||
|
|
||||||
- **Mobile (free, ads removable via IAP)**: [App Store](https://apps.apple.com/us/app/bauhaus-echo/id6759010657) | [Google Play](https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho)
|
Bauhaus Echo is now available on the [App Store](https://apps.apple.com/us/app/bauhaus-echo/id6759010657), [Google Play](https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho), [itch.io](https://suchsoftware.itch.io/bauhaus-echo), and [the web](https://bauhaus.such.software). It is a visual memory game where you memorize tile arrangements, watch them shuffle, and reconstruct the original pattern.
|
||||||
- **Desktop ($2, no ads)**: [itch.io](https://suchsoftware.itch.io/bauhaus-echo)
|
|
||||||
- **Web (free)**: [bauhaus.such.software](https://bauhaus.such.software)
|
|
||||||
|
|
||||||
## The Concept
|
## The Concept
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ date: "2026-03-26"
|
|||||||
excerpt: "AI-powered decision making app with subscriptions and ad monetization: now on Android and iOS"
|
excerpt: "AI-powered decision making app with subscriptions and ad monetization: now on Android and iOS"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<SuchoiceStoreButtons />
|
||||||
|
|
||||||
Suchoice is now available on [Google Play](https://play.google.com/store/apps/details?id=com.suchsoftware.suchoice) and the [App Store](https://apps.apple.com/us/app/suchoice/id6759626658). Point your camera at a menu, list, or anything with options. AI reads them, loads them into a wheel, and you spin.
|
Suchoice is now available on [Google Play](https://play.google.com/store/apps/details?id=com.suchsoftware.suchoice) and the [App Store](https://apps.apple.com/us/app/suchoice/id6759626658). Point your camera at a menu, list, or anything with options. AI reads them, loads them into a wheel, and you spin.
|
||||||
|
|
||||||
## Four Ways to Decide
|
## Four Ways to Decide
|
||||||
@@ -36,7 +38,6 @@ Subscription state is managed through RevenueCat with a single "premium" entitle
|
|||||||
|
|
||||||
## Get It
|
## Get It
|
||||||
|
|
||||||
- [Google Play](https://play.google.com/store/apps/details?id=com.suchsoftware.suchoice)
|
<SuchoiceStoreButtons />
|
||||||
- [App Store](https://apps.apple.com/us/app/suchoice/id6759626658)
|
|
||||||
|
|
||||||
See the [Suchoice product page](/products/suchoice) for more.
|
See the [Suchoice product page](/products/suchoice) for more.
|
||||||
|
|||||||
@@ -1,73 +1,52 @@
|
|||||||
---
|
---
|
||||||
title: "Vegan IQ: Test Your Plant-Based Knowledge"
|
title: "Vegan IQ: 1,066 Data-Backed Trivia Questions"
|
||||||
date: "2026-04-08"
|
date: "2026-04-08"
|
||||||
excerpt: "1000+ trivia questions across 8 categories, all backed by verified data. Now on Android and iOS."
|
excerpt: "A plant-based trivia game built on USDA, Oxford, NIH, and FAOSTAT data. Flutter, Python data pipeline, deterministic question generation. Android and iOS."
|
||||||
---
|
---
|
||||||
|
|
||||||
Vegan IQ is a trivia game that tests what you know about plant-based food, nutrition, and sustainability. Over 1,000 questions across 8 categories, all grounded in real data from the USDA, Poore & Nemecek (2018), NIH, and FAOSTAT.
|
<VeganIqStoreButtons />
|
||||||
|
|
||||||
## Why We Built It
|
Vegan IQ is a trivia game about plant-based food, nutrition, and sustainability. 1,066 questions across 8 categories. Every answer traces back to a specific data source -- USDA FoodData Central, Poore & Nemecek (Science, 2018), NIH, or FAOSTAT. No LLM-generated content.
|
||||||
|
|
||||||
There is a lot of information out there about veganism, plant-based nutrition, and the environmental impact of food. Some of it is accurate, some of it is not. We wanted to make a game where every question is backed by verified data -- a fun way to learn real facts about what you eat and how it affects the planet.
|
## Data Pipeline
|
||||||
|
|
||||||
No LLM-generated content. Every question traces back to a specific data source.
|
Questions are generated deterministically from structured JSON data files by a Python pipeline. The pipeline pulls nutritional composition from USDA, environmental impact metrics (water, land, GHG per kg) from Poore & Nemecek, health data from NIH, and agricultural production stats from FAOSTAT.
|
||||||
|
|
||||||
|
Raw generation produces ~2,461 candidates. Subcategory caps, `MINIMUM_MEANINGFUL` thresholds (protein >= 5g, fiber >= 1g, iron >= 1.5mg, calcium >= 30mg, vitamin C >= 5mg), and deduplication reduce this to 1,066 curated questions. A `primary_food` field on 426 questions lets the selection engine penalize same-food repetition within a single quiz (-100 weight).
|
||||||
|
|
||||||
|
Template-generated questions account for 43% of the pool; the remaining 57% are hand-curated.
|
||||||
|
|
||||||
|
## Question Selection
|
||||||
|
|
||||||
|
An anti-repetition pacing engine selects questions at runtime. The algorithm tracks recently-seen questions and applies decay weights so quizzes feel fresh even across multiple sessions. A spaced repetition layer re-surfaces questions the player got wrong more frequently.
|
||||||
|
|
||||||
|
Category selection uses dynamic counts with a 2-column grid UI. Plant milk questions are excluded from environmental per-kg generators via `_ENV_PER_KG_EXCLUDE` to avoid misleading comparisons.
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
|
||||||
|
This release adds:
|
||||||
|
|
||||||
|
- **Streaks and stats** -- daily streaks, lifetime stats, per-category performance tracking
|
||||||
|
- **Titles and badges** -- progression from "Sprout" to "Sage", plus category-specific badges with artist-drawn assets
|
||||||
|
- **Share cards** -- off-screen `RepaintBoundary` renders branded PNG at 2x resolution, shared via `share_plus` with 5-minute delayed cleanup. Fallback to plain text if image generation fails.
|
||||||
|
- **Onboarding flow** -- swipe/tap tutorial for new players
|
||||||
|
- **Quiz length picker** -- 10, 15, or 20 questions per session
|
||||||
|
|
||||||
## 8 Categories
|
## 8 Categories
|
||||||
|
|
||||||
- **Nutrition**: macronutrients, vitamins, minerals, and dietary facts about plant-based foods
|
Nutrition, Environment, Ingredients, Food History, Animal Welfare, Health, Cooking, Culture.
|
||||||
- **Environment**: water use, land use, greenhouse gas emissions, and ecological impact of food production
|
|
||||||
- **Ingredients**: what is and is not vegan, hidden animal products, and food labeling
|
|
||||||
- **Food History**: origins and cultural history of plant-based foods
|
|
||||||
- **Animal Welfare**: facts about farming practices and animal agriculture
|
|
||||||
- **Health**: plant-based diets and health outcomes from peer-reviewed research
|
|
||||||
- **Cooking**: techniques, substitutions, and preparation of plant-based foods
|
|
||||||
- **Culture**: veganism around the world, traditions, and cultural practices
|
|
||||||
|
|
||||||
## How It Works
|
## Stack
|
||||||
|
|
||||||
Swipe right for true, left for false. Tap your answer for multiple choice questions. Sound effects and haptic feedback make every answer feel satisfying. Choose your quiz length -- 10, 15, or 20 questions -- and pick any category or mix them all.
|
- **Client**: Flutter/Dart, single codebase for Android and iOS
|
||||||
|
|
||||||
## What's in v1.1.0
|
|
||||||
|
|
||||||
- **Streaks and stats** -- daily streaks, lifetime stats, and category-level performance tracking
|
|
||||||
- **Titles and badges** -- earn titles like "Sprout" through "Sage" as you level up, plus category-specific badges
|
|
||||||
- **Share cards** -- share interesting facts or your score as branded images directly from the app
|
|
||||||
- **Onboarding flow** -- new players get a quick walkthrough of how the game works
|
|
||||||
- **Anti-repetition engine** -- smarter question selection so quizzes feel fresh every time
|
|
||||||
- **Spaced repetition** -- questions you get wrong come back more often until you learn them
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
- **1,000+ curated questions** across 8 categories
|
|
||||||
- **Swipe & tap gameplay** -- intuitive and fast
|
|
||||||
- **Dark and light mode** -- play however you prefer
|
|
||||||
- **SFX & haptics** -- satisfying audio and tactile feedback
|
|
||||||
- **Share facts** -- send interesting questions to friends as images or text
|
|
||||||
- **Fully offline** -- all questions bundled in the app
|
|
||||||
- **No account required** -- open and play
|
|
||||||
|
|
||||||
## Data Integrity
|
|
||||||
|
|
||||||
All questions are generated deterministically from structured data files. The data pipeline is written in Python and pulls from:
|
|
||||||
|
|
||||||
- **USDA FoodData Central** for nutritional composition
|
|
||||||
- **Poore & Nemecek, Science (2018)** for environmental impact metrics
|
|
||||||
- **NIH** for health and dietary reference data
|
|
||||||
- **FAOSTAT** for global agricultural statistics
|
|
||||||
|
|
||||||
Questions are not written by an LLM. The pipeline generates them from verified numbers and facts, ensuring accuracy.
|
|
||||||
|
|
||||||
## Tech
|
|
||||||
|
|
||||||
- **Client**: Flutter (Dart), single codebase for Android and iOS
|
|
||||||
- **Data**: Python pipeline generating JSON question assets
|
- **Data**: Python pipeline generating JSON question assets
|
||||||
- **Ads**: Google AdMob (banner, interstitial)
|
- **Ads**: Google AdMob (banner, interstitial)
|
||||||
- **IAP**: Apple StoreKit and Google Play Billing via in_app_purchase
|
- **IAP**: StoreKit + Google Play Billing via `in_app_purchase`
|
||||||
- **Storage**: shared_preferences for local score and preference data
|
- **Storage**: `shared_preferences` for local scores and preferences
|
||||||
|
- **Share**: `share_plus` + off-screen `RepaintBoundary` for image generation
|
||||||
|
|
||||||
## Get It
|
## Get It
|
||||||
|
|
||||||
- [Google Play](https://play.google.com/store/apps/details?id=com.veganiq.vegan_iq)
|
<VeganIqStoreButtons />
|
||||||
- [App Store](https://apps.apple.com/us/app/vegan-iq-plant-based-trivia/id6761139580)
|
|
||||||
|
|
||||||
See the [Vegan IQ product page](/products/vegan-iq) for more.
|
See the [Vegan IQ product page](/products/vegan-iq) for more.
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { Header } from "@/components/layout/header";
|
import { Header } from "@/components/layout/header";
|
||||||
import { MobileNav } from "@/components/layout/mobile-nav";
|
import { MobileNav } from "@/components/layout/mobile-nav";
|
||||||
|
import {
|
||||||
|
StoreButtons,
|
||||||
|
VeganIqStoreButtons,
|
||||||
|
SuchoiceStoreButtons,
|
||||||
|
BauhausEchoStoreButtons,
|
||||||
|
} from "@/components/store-buttons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
@@ -58,7 +64,12 @@ export default async function BlogPost({ params }: Props) {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="prose dark:prose-invert max-w-none">
|
<div className="prose dark:prose-invert max-w-none">
|
||||||
<MDXRemote source={post.content} />
|
<MDXRemote source={post.content} components={{
|
||||||
|
StoreButtons,
|
||||||
|
VeganIqStoreButtons,
|
||||||
|
SuchoiceStoreButtons,
|
||||||
|
BauhausEchoStoreButtons,
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Header } from "@/components/layout/header";
|
import { Header } from "@/components/layout/header";
|
||||||
import { MobileNav } from "@/components/layout/mobile-nav";
|
import { MobileNav } from "@/components/layout/mobile-nav";
|
||||||
import { Card } from "@repo/ui/components/card";
|
import { Card } from "@repo/ui/components/card";
|
||||||
import { Button } from "@repo/ui/components/button";
|
import { StoreButtons } from "@/components/store-buttons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
@@ -43,25 +43,17 @@ export default function BauhausEchoPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-4 mb-12">
|
<div className="mb-12">
|
||||||
<a href="https://bauhaus.such.software" target="_blank" rel="noopener noreferrer">
|
<StoreButtons
|
||||||
<Button size="lg">Play on Web</Button>
|
links={[
|
||||||
</a>
|
{ platform: "apple", href: "https://apps.apple.com/us/app/bauhaus-echo/id6759010657" },
|
||||||
<a href="https://apps.apple.com/us/app/bauhaus-echo/id6759010657" target="_blank" rel="noopener noreferrer">
|
{ platform: "google", href: "https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho" },
|
||||||
<Button variant="outline" size="lg">
|
]}
|
||||||
Download on App Store
|
extraLinks={[
|
||||||
</Button>
|
{ href: "https://bauhaus.such.software", label: "Play on Web" },
|
||||||
</a>
|
{ href: "https://suchsoftware.itch.io/bauhaus-echo", label: "itch.io — $2" },
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho" target="_blank" rel="noopener noreferrer">
|
]}
|
||||||
<Button variant="outline" size="lg">
|
/>
|
||||||
Get it on Google Play
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
<a href="https://suchsoftware.itch.io/bauhaus-echo" target="_blank" rel="noopener noreferrer">
|
|
||||||
<Button variant="outline" size="lg">
|
|
||||||
Buy on itch.io — $2
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold mb-6">How It Works</h2>
|
<h2 className="text-2xl font-bold mb-6">How It Works</h2>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Header } from "@/components/layout/header";
|
import { Header } from "@/components/layout/header";
|
||||||
import { MobileNav } from "@/components/layout/mobile-nav";
|
import { MobileNav } from "@/components/layout/mobile-nav";
|
||||||
import { Card } from "@repo/ui/components/card";
|
import { Card } from "@repo/ui/components/card";
|
||||||
import { Button } from "@repo/ui/components/button";
|
import { StoreButtons } from "@/components/store-buttons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
@@ -43,17 +43,11 @@ export default function SuchoicePage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-4 mb-12">
|
<div className="mb-12">
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.suchsoftware.suchoice" target="_blank" rel="noopener noreferrer">
|
<StoreButtons links={[
|
||||||
<Button variant="outline" size="lg">
|
{ platform: "google", href: "https://play.google.com/store/apps/details?id=com.suchsoftware.suchoice" },
|
||||||
Google Play
|
{ platform: "apple", href: "https://apps.apple.com/us/app/suchoice/id6759626658" },
|
||||||
</Button>
|
]} />
|
||||||
</a>
|
|
||||||
<a href="https://apps.apple.com/us/app/suchoice/id6759626658" target="_blank" rel="noopener noreferrer">
|
|
||||||
<Button variant="outline" size="lg">
|
|
||||||
App Store
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold mb-6">Features</h2>
|
<h2 className="text-2xl font-bold mb-6">Features</h2>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Header } from "@/components/layout/header";
|
import { Header } from "@/components/layout/header";
|
||||||
import { MobileNav } from "@/components/layout/mobile-nav";
|
import { MobileNav } from "@/components/layout/mobile-nav";
|
||||||
import { Card } from "@repo/ui/components/card";
|
import { Card } from "@repo/ui/components/card";
|
||||||
import { Button } from "@repo/ui/components/button";
|
import { StoreButtons } from "@/components/store-buttons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
@@ -45,17 +45,11 @@ export default function VeganIqPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-4 mb-12">
|
<div className="mb-12">
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.veganiq.vegan_iq" target="_blank" rel="noopener noreferrer">
|
<StoreButtons links={[
|
||||||
<Button variant="outline" size="lg">
|
{ platform: "google", href: "https://play.google.com/store/apps/details?id=com.veganiq.vegan_iq" },
|
||||||
Google Play
|
{ platform: "apple", href: "https://apps.apple.com/us/app/vegan-iq-plant-based-trivia/id6761139580" },
|
||||||
</Button>
|
]} />
|
||||||
</a>
|
|
||||||
<a href="https://apps.apple.com/us/app/vegan-iq-plant-based-trivia/id6761139580" target="_blank" rel="noopener noreferrer">
|
|
||||||
<Button variant="outline" size="lg">
|
|
||||||
App Store
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold mb-6">Features</h2>
|
<h2 className="text-2xl font-bold mb-6">Features</h2>
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { FaApple, FaGooglePlay } from "react-icons/fa6";
|
||||||
|
import { Button } from "@repo/ui/components/button";
|
||||||
|
|
||||||
|
type StoreLink = {
|
||||||
|
platform: "apple" | "google";
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StoreButtonsProps = {
|
||||||
|
links: StoreLink[];
|
||||||
|
extraLinks?: { href: string; label: string }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const platformConfig = {
|
||||||
|
apple: { icon: FaApple, label: "App Store" },
|
||||||
|
google: { icon: FaGooglePlay, label: "Google Play" },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function StoreButtons({ links, extraLinks }: StoreButtonsProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4 not-prose">
|
||||||
|
{links.map(({ platform, href }) => {
|
||||||
|
const { icon: Icon, label } = platformConfig[platform];
|
||||||
|
return (
|
||||||
|
<a key={platform} href={href} target="_blank" rel="noopener noreferrer">
|
||||||
|
<Button variant="outline" size="lg">
|
||||||
|
<Icon className="h-5 w-5 mr-2" aria-hidden="true" />
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{extraLinks?.map(({ href, label }) => (
|
||||||
|
<a key={href} href={href} target="_blank" rel="noopener noreferrer">
|
||||||
|
<Button variant="outline" size="lg">
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-configured components for use in MDX blog posts
|
||||||
|
export function VeganIqStoreButtons() {
|
||||||
|
return (
|
||||||
|
<StoreButtons links={[
|
||||||
|
{ platform: "google", href: "https://play.google.com/store/apps/details?id=com.veganiq.vegan_iq" },
|
||||||
|
{ platform: "apple", href: "https://apps.apple.com/us/app/vegan-iq-plant-based-trivia/id6761139580" },
|
||||||
|
]} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SuchoiceStoreButtons() {
|
||||||
|
return (
|
||||||
|
<StoreButtons links={[
|
||||||
|
{ platform: "google", href: "https://play.google.com/store/apps/details?id=com.suchsoftware.suchoice" },
|
||||||
|
{ platform: "apple", href: "https://apps.apple.com/us/app/suchoice/id6759626658" },
|
||||||
|
]} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BauhausEchoStoreButtons() {
|
||||||
|
return (
|
||||||
|
<StoreButtons
|
||||||
|
links={[
|
||||||
|
{ platform: "apple", href: "https://apps.apple.com/us/app/bauhaus-echo/id6759010657" },
|
||||||
|
{ platform: "google", href: "https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho" },
|
||||||
|
]}
|
||||||
|
extraLinks={[
|
||||||
|
{ href: "https://bauhaus.such.software", label: "Play on Web" },
|
||||||
|
{ href: "https://suchsoftware.itch.io/bauhaus-echo", label: "itch.io — $2" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user