Files
flixcooks-website/CLAUDE.md
T
smacks d53fd8742d Claude Init und Readme Update
Migration auf Docker Umgebung
2026-07-26 09:56:58 +02:00

7.5 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project overview

FlixCooks is a premium recipe/food-blog website built as vanilla server-rendered PHP — no framework, no build step, no JS bundler, no Composer dependencies. PHP renders HTML directly with heavy inline <script>/<style> blocks per page. All data lives in PostgreSQL (no JSON files, no ORM).

Commands

There is no build step, package manager, linter, or test suite in this repo (no composer.json, no phpunit, no package.json). Work is verified by running the app directly.

# Local app server
php -S localhost:8000

# Local Postgres (separate from production)
docker compose -f docker-compose.dev.yml up -d
docker compose -f docker-compose.dev.yml ps        # wait for healthy
docker compose -f docker-compose.dev.yml down -v   # wipe + re-seed on next run

# .env setup (never commit .env)
cp .env.example .env   # then set DATABASE_URL

# Seed / verify DB
php scripts/db-seed.php     # one-time import of data/recipes.json into Postgres
php scripts/db-check.php    # connect, init schema, print recipe count

# Inspect DB directly
docker exec -it flixcooks-postgres-dev psql -U flixcooks -d flixcooks_dev

# Full stack via Docker (production-like image)
docker compose build && docker compose up -d   # http://127.0.0.1:8080

Requires PHP 8.x with the pgsql/pdo_pgsql extension (sudo apt install php-pgsql on Debian/Ubuntu). Without a working DATABASE_URL, every page returns HTTP 503 and renders maintenance/db-unavailable.php — there is no file/JSON fallback.

Architecture

Request flow

Every entry-point page (index.php, recipe.php, admin.php, login.php) follows the same pattern:

  1. require __DIR__ . '/helpers.php' (which itself requires config.php and starts the session).
  2. Call load_recipes() / load_site_settings() inside a try/catch (DatabaseUnavailableException $e) that calls handle_database_unavailable($e) on failure.
  3. Resolve $lang from ?lang=de|en (default en) and build a $copy/$t array of inline translation strings for that page.
  4. Render HTML directly (partials/head.phppartials/header.php → page body → partials/footer.php), reading from helpers.php data structures.

There is no router and no templating engine — each .php file is both controller and view.

Data layer (config.php + helpers.php)

  • config.php: parses .env (load_env()), exposes get_db_connection() (PDO, memoized in a static var) and DatabaseUnavailableException.
  • helpers.php: everything else — schema bootstrap (ensure_recipe_schema() applies scripts/schema.sql idempotently on first DB use, no migrations system), and CRUD:
    • load_recipes() / load_recipe_by_slug() — hydrate recipes from normalized tables into the nested PHP array shape templates expect (hydrate_recipes_from_db()).
    • save_recipe() / delete_recipe() — used by admin.php; save_recipe() deletes+reinserts child rows (translations/tags/ingredients/utensils/steps) inside a transaction rather than diffing.
    • load_site_settings() / save_site_settings() — imprint/privacy legal-page copy, stored as EAV rows (site_settings(section, lang, setting_key, setting_value)), merged over default_site_settings().
    • localize_recipe() / localize_recipes() — flatten a recipe's i18n[lang] block onto the top level for the current request's language.
    • e() — the only HTML-escaping helper (htmlspecialchars wrapper); always use it when echoing user- or DB-sourced strings.

Recipe data shape

A recipe row + its children hydrate into:

[
  'slug', 'hero', 'prep_time', 'cook_time', 'total_time', 'servings',
  'featured', 'coming_soon',
  'nutrition' => ['calories','protein','carbs','fat'],
  'i18n' => [
    'en' => ['title','description','category','difficulty','tags','ingredients','utensils','steps','step_videos','step_timers'],
    'de' => [ ... same shape ... ],
  ],
]

step_videos and step_timers are parallel arrays indexed the same as steps (one optional video URL / timer-in-minutes per step). In admin.php these are edited as one-line-per-array-element <textarea> fields.

Database schema (scripts/schema.sql)

Normalized Postgres tables, all applied via ensure_recipe_schema() (not a migrations tool — editing the schema means editing this file, which must stay idempotent CREATE TABLE IF NOT EXISTS): recipes (1 row per recipe) → recipe_translations, recipe_tags, recipe_ingredients, recipe_utensils, recipe_steps (all keyed by recipe_slug + lang, cascade-deleted with the parent recipe) → site_settings (imprint/privacy copy, keyed by section + lang + setting_key).

Auth

admin.php guards itself with a single shared secret (FLIXCOOKS_ADMIN_KEY env var, compared via hash_equals) rather than user accounts; success sets $_SESSION['fc_admin'] = true. Admin POST handlers additionally check a CSRF token ($_SESSION['csrf_token'] vs $_POST['token']). If FLIXCOOKS_ADMIN_KEY is unset, admin.php renders an "unavailable" page instead of a login form.

Frontend conventions

  • No JS build step — GSAP, ScrollTrigger, and Lenis are loaded from CDN in partials/head.php; page-specific behavior lives in inline <script> blocks at the bottom of each .php file.
  • assets/fc-local.js is the only standalone JS file — client-side localStorage for favorites and dietary-goal personalization (no backend user accounts on the public site).
  • Scroll-reveal: elements tagged .reveal-target are animated in via a shared IntersectionObserver pattern repeated per-page (see bottom of index.php / recipe.php).
  • Lenis smooth scroll must be explicitly stopped/started around fullscreen overlays: window.lenis.stop() on open, window.lenis.start() on close (see Cooking Mode in recipe.php).
  • Styling is one large assets/style.css using CSS custom properties, clamp()-based fluid spacing/typography, and a 24-column asymmetric grid (FloemaLayoutGrid). Full visual language (palette options, motion specs, named components like CapitoliumRevealButton, LiquidOverlayMenu, AuraMarbleBackground) is documented in .agents/DESIGN_GUIDE.md — consult it before styling new UI so new work matches the established aesthetic vocabulary.
  • Assets are cache-busted via filemtime() query strings ($assetVersion() in partials/head.php), not filename hashing.

Deployment

Single production Dockerfile (php:8.3-apache-bookworm), pushed to Coolify. docker/entrypoint.sh waits for the DB (scripts/db-check.php polling loop) and applies schema before starting Apache. health.php is the container HEALTHCHECK target. Details in docs/COOLIFY.md.

Project workflow rules (from .agents/rules/)

  • Never commit directly to main. All work happens on feature branches (feature/... or issue-#...), merged via PR. Pin GitHub Actions to specific version tags, not @latest.
  • Merge-conflict resolution on a feature branch is the responsibility of that branch's author — merge main in, resolve manually, never force-push over others' work.
  • The close_feature skill (.agents/skills/close_feature.json) encodes the "merge feature → main, verify, push, delete branch" flow.
  • Track development progress in .agents/TODO.md; record new architectural/aesthetic/workflow knowledge in .agents/brain.md (both are living documents future agents rely on — update them as you learn things, don't just read them).
  • Non-code-affecting markdown docs can be committed straight to main.