georgeandClaude Opus 5 b9a4e16892 Add the Adjectives section, and explain why the noun itself changes
Two gaps, both visible from one screenshot of the Genitiv level.

1. Adjective endings were half-present and entirely untested. STRONG and
   WEAK existed in engine.js with correct data, but only as the S/W columns
   of a reference chart — nothing generated them, nothing tested them, and
   the MIXED table (the one "in seinem eigenen Kontext" actually needs) did
   not exist at all. There was not a single adjective in the codebase.

2. Nothing explained why "Regen" becomes "Regens" after trotz.

New Adjectives tab, six lessons plus Daily Practice:

  Schwach          after der/diese/jede — only -e and -en
  Gemischt         after ein/kein/mein — weak plus three exceptions
  Stark            no determiner — the adjective carries the case
  Welche Tabelle?  der-word, ein-word or nothing? (the meta-skill)
  Nach Präposition the full chain: preposition -> case -> table
  Alles gemischt   all three tables, including Genitiv

The three tables are built on one idea: the case has to be signalled once
in the noun phrase, and whoever can show it does. MIXED is therefore not
hand-typed — it is DERIVED from POSS_ENDINGS as "weak, except in the three
slots where the ein-word has no ending of its own, where it borrows the
strong one". A test asserts that relationship as well as all 48 cells, so
the rule cannot silently rot.

Two info panels:
- "Which ending table?" — the one-signal idea, the three tables, plus the
  two traps: strong genitive masc/neut is -en not -es (the noun already
  carries the -(e)s), and predicate adjectives take no ending at all.
- "Why Regens?" — appears wherever the case changes the noun itself:
  the -es/-s split, that feminines and plurals never change, the dative
  plural -n, and a note on the N-declension nouns Beug does not have yet.

Notable decisions:
- Nominative is now tested, for adjectives only. der gute Mann vs ein
  guter Mann is the clearest illustration of the whole weak/mixed split and
  it exists only in the nominative, so the app-wide "no nominative" rule is
  explicitly scoped to non-adjective levels rather than quietly dropped.
- The tab bar measured 369px of content in a 350px bar with four tabs, so
  a fifth could not fit. The row now scrolls instead of squeezing labels.
- Adjectives are gated like Possessives and Pronouns.

The corpus caught its keep again. Adjective sentences are enumerated into
corpus.txt keyed on the semantic pairing (frame x adjective x noun), which
keeps it at ~2000 lines instead of exploding over every determiner and
case. Reading it surfaced, and this commit fixes: "I live in the old lake"
(places split into buildings/settlements/landscape), "I play with the blue
sofa", "I like the bad kitchen" (frames that assert liking now exclude
negative adjectives), "I buy the good time" and "I cook with the good
money" (pools narrowed to what you can buy and cook with), grosser/kleiner
of a person rendering as "big/small" rather than "tall/short", and
"in dem alten Hotel" failing to fuse into "im alten Hotel".

130 tests pass; 12000 generated questions across all 30 levels.

Known gap, stated in the rule panel: Beug has no plural nouns, so the
plural column of all three tables is shown for reference but not drilled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-09-22 06:06:59 +00:00
2026-07-05 12:48:27 +02:00
2026-07-05 12:48:27 +02:00

Deklinationstrainer

A daily-practice German declension trainer. Self-hosted so your progress actually saves.

Deploy to Coolify

One-time setup

  1. Push these files to a git repo (GitHub / Gitea / whatever Coolify is connected to).

  2. In Coolify, create a new Application:

    • Source: your git repo
    • Build pack: Nixpacks (auto-detects Vite) or Static
    • Build command: npm run build
    • Publish directory: dist
    • Port: not needed for static output (Coolify serves it directly)
  3. Add a domain (e.g. deklination.yourdomain.com). Coolify provisions HTTPS via Caddy/Traefik automatically.

  4. Deploy. Visit the URL on any device.

Local dev

npm install
npm run dev    # http://localhost:5173
npm run build  # outputs to ./dist

On iPhone

Open the URL in Safari, tap Share → Add to Home Screen. It opens fullscreen like a native app, and progress saves in localStorage across reboots, sessions, and app closes.

Storage

The app tries window.storage first (Claude artifact environment), then falls back to localStorage. When self-hosted, only localStorage runs, which iOS Safari persists reliably.

To wipe progress: open browser devtools → Application → Local Storage → delete the gd-v10 key. Or on iPhone: Settings → Safari → Advanced → Website Data → search your domain → delete.

Stripe checkout

Beug uses public Stripe Checkout or Payment Link URLs, supplied through environment variables. Do not put Stripe secret keys in this Vite app.

Payments are behind a launch-mode flag:

VITE_PAYMENTS_ENABLED=false

When VITE_PAYMENTS_ENABLED is absent or false, the app runs in Reddit/free mode: all lessons are usable, paid rows are not blurred, and the paywall/pricing screen is hidden. The Stripe URLs can stay configured in the environment so billing can be switched back on later with one env change.

To turn payments back on, set:

VITE_PAYMENTS_ENABLED=true

Create one product in Stripe, for example Beug Premium, with three prices:

  • Weekly subscription: $2.99 every week
  • Yearly subscription: $9.99 every year
  • Lifetime access: $19.99 one-time payment

Create a Payment Link or Checkout link for each price. Set the success redirect URL to:

https://beug.george1.dev/?checkout=success

Set the cancel URL to:

https://beug.george1.dev/

Then add these public URLs in the deployment environment:

VITE_STRIPE_YEARLY_URL=https://buy.stripe.com/...
VITE_STRIPE_LIFETIME_URL=https://buy.stripe.com/...
VITE_STRIPE_WEEKLY_URL=https://buy.stripe.com/...

The current v1 unlock is intentionally simple: after Stripe redirects back with ?checkout=success, Beug stores the unlock in this browser's localStorage. This avoids accounts for the tiny-app version, but it is a soft paywall. A stricter version needs a small backend, Stripe webhooks, and account or email-based entitlement lookup.

Future prompt to re-enable payments

Turn payments back on for Beug. Set VITE_PAYMENTS_ENABLED=true in Coolify, keep the three existing Stripe Payment Link env vars, redeploy the app, and verify that unpaid users see the soft paid lesson blur, that charts remain visible, and that tapping a paid lesson opens the pricing screen with Weekly, Yearly, and Lifetime plans.
S
Description
No description provided
Readme
4 MiB
Languages
JavaScript 99.7%
HTML 0.2%