diff --git a/apps/app_seeds/dart_test.yaml b/apps/app_seeds/dart_test.yaml index a7c652f..8fac206 100644 --- a/apps/app_seeds/dart_test.yaml +++ b/apps/app_seeds/dart_test.yaml @@ -11,3 +11,13 @@ # # A genuinely slower (but finite) test can opt out with @Timeout(Duration(...)). timeout: 90s + +tags: + # Localized store/landing screenshots (test/screenshots/). These are asset + # GENERATION, not a correctness gate, and golden pixel-matching is sensitive + # to OS/font/Flutter version — so they must NOT run in the normal suite (incl. + # CI's `flutter test --coverage`). Run them explicitly: + # flutter test --run-skipped --tags screenshots test/screenshots/ + # add --update-goldens to (re)write the PNGs. + screenshots: + skip: "asset generation; run with --run-skipped --tags screenshots" diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png new file mode 100644 index 0000000..820f88f Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png new file mode 100644 index 0000000..714a531 Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png new file mode 100644 index 0000000..456b140 Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png new file mode 100644 index 0000000..475e75f Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png new file mode 100644 index 0000000..33ee8a4 Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/1.png b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/1.png new file mode 100644 index 0000000..f39ef76 Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/1.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/2.png b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/2.png new file mode 100644 index 0000000..7cd04c5 Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/2.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/3.png b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/3.png new file mode 100644 index 0000000..dd12bce Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/3.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/4.png b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/4.png new file mode 100644 index 0000000..f5387b0 Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/4.png differ diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/5.png b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/5.png new file mode 100644 index 0000000..13eaf36 Binary files /dev/null and b/apps/app_seeds/fastlane/metadata/android/es-ES/images/phoneScreenshots/5.png differ diff --git a/apps/app_seeds/test/screenshots/README.md b/apps/app_seeds/test/screenshots/README.md new file mode 100644 index 0000000..ee54082 --- /dev/null +++ b/apps/app_seeds/test/screenshots/README.md @@ -0,0 +1,43 @@ +# Localized app screenshots + +Golden-based harness that renders key screens as PNGs in several languages, for +the landing site (`tane.comunes.org`) and the app-store listings. No emulator — +it runs under `flutter test`, so it's deterministic and CI-friendly. + +## Regenerate + +```sh +cd apps/app_seeds +flutter test --update-goldens --run-skipped --tags screenshots test/screenshots/screenshots_test.dart +tool/collect_screenshots.sh # copy PNGs into site/ and fastlane/ +``` + +Drop `--update-goldens` to *verify* the committed PNGs still match. The +`screenshots` tag is **skipped by default** (`dart_test.yaml`) — so it never +runs in the normal suite or CI's `flutter test --coverage`, where golden pixel +differences across OS/font versions would cause false failures. `--run-skipped` +opts it back in. + +## What it produces + +`goldens//.png` for **en, es, fr, de, pt, ja**, screens +**home · inventory · market · calendar · detail**, plus `goldens/rtl/inventory.png`. +The market is rendered populated via a seeded `OffersCubit` with a no-op transport +(no network); home/calendar/detail/inventory render from the repository alone. + +- Fonts: real glyphs come from `loadScreenshotFonts()` — every family in + `FontManifest.json` (MaterialIcons + bundled Noto/seedks) plus the bundled + **DejaVu Sans** as the Latin text face. No Roboto needed. +- `rtl/` is a **layout demo**, not a locale: English strings forced + right-to-left to prove the design is RTL-safe. The app ships no Arabic + translation yet, so there is deliberately no `ar/` with Arabic copy. +- Partial locales (e.g. `ja`) fall back to English for untranslated strings — + that's the app's real runtime behavior, shown honestly. +- These are raw device-canvas frames (no marketing captions or device bezels). + Add those downstream only if a store rejects plain screenshots. + +## Adding a screen or locale + +Edit `screenshotLocales` / the `screens` map in `screenshots_test.dart`. Screens +must render from the repository + cubits alone (no live Nostr/network stack), so +inventory/detail/calendar are the safe, self-contained set. diff --git a/apps/app_seeds/test/screenshots/goldens/de/calendar.png b/apps/app_seeds/test/screenshots/goldens/de/calendar.png new file mode 100644 index 0000000..009eb81 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/de/calendar.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/de/detail.png b/apps/app_seeds/test/screenshots/goldens/de/detail.png new file mode 100644 index 0000000..3455f0a Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/de/detail.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/de/home.png b/apps/app_seeds/test/screenshots/goldens/de/home.png new file mode 100644 index 0000000..5be2c15 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/de/home.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/de/inventory.png b/apps/app_seeds/test/screenshots/goldens/de/inventory.png new file mode 100644 index 0000000..2533939 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/de/inventory.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/de/market.png b/apps/app_seeds/test/screenshots/goldens/de/market.png new file mode 100644 index 0000000..b6413ef Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/de/market.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/en/calendar.png b/apps/app_seeds/test/screenshots/goldens/en/calendar.png new file mode 100644 index 0000000..475e75f Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/en/calendar.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/en/detail.png b/apps/app_seeds/test/screenshots/goldens/en/detail.png new file mode 100644 index 0000000..33ee8a4 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/en/detail.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/en/home.png b/apps/app_seeds/test/screenshots/goldens/en/home.png new file mode 100644 index 0000000..820f88f Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/en/home.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/en/inventory.png b/apps/app_seeds/test/screenshots/goldens/en/inventory.png new file mode 100644 index 0000000..714a531 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/en/inventory.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/en/market.png b/apps/app_seeds/test/screenshots/goldens/en/market.png new file mode 100644 index 0000000..456b140 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/en/market.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/es/calendar.png b/apps/app_seeds/test/screenshots/goldens/es/calendar.png new file mode 100644 index 0000000..f5387b0 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/es/calendar.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/es/detail.png b/apps/app_seeds/test/screenshots/goldens/es/detail.png new file mode 100644 index 0000000..13eaf36 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/es/detail.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/es/home.png b/apps/app_seeds/test/screenshots/goldens/es/home.png new file mode 100644 index 0000000..f39ef76 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/es/home.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/es/inventory.png b/apps/app_seeds/test/screenshots/goldens/es/inventory.png new file mode 100644 index 0000000..7cd04c5 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/es/inventory.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/es/market.png b/apps/app_seeds/test/screenshots/goldens/es/market.png new file mode 100644 index 0000000..dd12bce Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/es/market.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/fr/calendar.png b/apps/app_seeds/test/screenshots/goldens/fr/calendar.png new file mode 100644 index 0000000..24cdb00 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/fr/calendar.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/fr/detail.png b/apps/app_seeds/test/screenshots/goldens/fr/detail.png new file mode 100644 index 0000000..634b1a3 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/fr/detail.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/fr/home.png b/apps/app_seeds/test/screenshots/goldens/fr/home.png new file mode 100644 index 0000000..17a7795 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/fr/home.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/fr/inventory.png b/apps/app_seeds/test/screenshots/goldens/fr/inventory.png new file mode 100644 index 0000000..ba4741c Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/fr/inventory.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/fr/market.png b/apps/app_seeds/test/screenshots/goldens/fr/market.png new file mode 100644 index 0000000..346b39d Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/fr/market.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/ja/calendar.png b/apps/app_seeds/test/screenshots/goldens/ja/calendar.png new file mode 100644 index 0000000..f833ae1 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/ja/calendar.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/ja/detail.png b/apps/app_seeds/test/screenshots/goldens/ja/detail.png new file mode 100644 index 0000000..33ee8a4 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/ja/detail.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/ja/home.png b/apps/app_seeds/test/screenshots/goldens/ja/home.png new file mode 100644 index 0000000..78b507e Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/ja/home.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/ja/inventory.png b/apps/app_seeds/test/screenshots/goldens/ja/inventory.png new file mode 100644 index 0000000..714a531 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/ja/inventory.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/ja/market.png b/apps/app_seeds/test/screenshots/goldens/ja/market.png new file mode 100644 index 0000000..456b140 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/ja/market.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/pt/calendar.png b/apps/app_seeds/test/screenshots/goldens/pt/calendar.png new file mode 100644 index 0000000..e5a5b8d Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/pt/calendar.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/pt/detail.png b/apps/app_seeds/test/screenshots/goldens/pt/detail.png new file mode 100644 index 0000000..dfa24bf Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/pt/detail.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/pt/home.png b/apps/app_seeds/test/screenshots/goldens/pt/home.png new file mode 100644 index 0000000..c0464e0 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/pt/home.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/pt/inventory.png b/apps/app_seeds/test/screenshots/goldens/pt/inventory.png new file mode 100644 index 0000000..cf033bd Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/pt/inventory.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/pt/market.png b/apps/app_seeds/test/screenshots/goldens/pt/market.png new file mode 100644 index 0000000..9e6b437 Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/pt/market.png differ diff --git a/apps/app_seeds/test/screenshots/goldens/rtl/inventory.png b/apps/app_seeds/test/screenshots/goldens/rtl/inventory.png new file mode 100644 index 0000000..2ec2b4e Binary files /dev/null and b/apps/app_seeds/test/screenshots/goldens/rtl/inventory.png differ diff --git a/apps/app_seeds/test/screenshots/screenshot_support.dart b/apps/app_seeds/test/screenshots/screenshot_support.dart new file mode 100644 index 0000000..4bdc7dd --- /dev/null +++ b/apps/app_seeds/test/screenshots/screenshot_support.dart @@ -0,0 +1,188 @@ +import 'dart:convert'; + +import 'package:commons_core/commons_core.dart'; +import 'package:drift/drift.dart' show Value; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show FontLoader, rootBundle; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tane/app.dart' show materialLocaleFor; +import 'package:tane/data/species_repository.dart'; +import 'package:tane/data/variety_repository.dart'; +import 'package:tane/db/database.dart'; +import 'package:tane/db/enums.dart'; +import 'package:tane/domain/crop_calendar.dart' show monthsToMask; +import 'package:tane/i18n/strings.g.dart'; +import 'package:tane/state/inventory_cubit.dart'; +import 'package:tane/ui/theme.dart'; + +/// Marketing screenshot canvas: a tall phone at 3x. Logical 360x760. +const screenshotSize = Size(1080, 2280); +const screenshotPixelRatio = 3.0; + +/// Locales we render real, translated screenshots for. Asturian (`ast`) is +/// omitted from store assets; the RTL demo is handled separately (the app has +/// no Arabic strings yet — see [ScreenshotLocale.rtlDemo]). +const screenshotLocales = [ + AppLocale.en, + AppLocale.es, + AppLocale.fr, + AppLocale.de, + AppLocale.pt, + AppLocale.ja, +]; + +bool _fontsLoaded = false; + +/// Registers real glyph-bearing fonts so golden screenshots show text and icons +/// rather than the Ahem/tofu boxes flutter_test uses by default. Loads every +/// family declared in the test bundle's FontManifest.json (MaterialIcons, the +/// bundled Noto Sans JP/Arabic and seedks glyphs), then adds DejaVu Sans — a +/// bundled asset, not a font *family* — as the Latin text face so en/es/fr/de/pt +/// render legibly without needing Roboto. +Future loadScreenshotFonts() async { + if (_fontsLoaded) return; + + Future family(String name, List assets) async { + final loader = FontLoader(name); + for (final asset in assets) { + loader.addFont(rootBundle.load(asset)); + } + await loader.load(); + } + + final manifest = + json.decode(await rootBundle.loadString('FontManifest.json')) + as List; + for (final entry in manifest.cast>()) { + await family( + entry['family'] as String, + [for (final f in entry['fonts'] as List) f['asset'] as String], + ); + } + + await family('DejaVu Sans', [ + 'assets/fonts/DejaVuSans.ttf', + 'assets/fonts/DejaVuSans-Bold.ttf', + ]); + _fontsLoaded = true; +} + +const _fontFallbacks = ['Noto Sans JP', 'Noto Sans Arabic']; + +/// The real app theme, but with every text style pinned to the bundled +/// [DejaVu Sans] so screenshots render legible glyphs (see [loadScreenshotFonts]). +ThemeData screenshotTheme() { + final base = buildTaneTheme(); + return base.copyWith( + textTheme: base.textTheme.apply( + fontFamily: 'DejaVu Sans', + fontFamilyFallback: _fontFallbacks, + ), + primaryTextTheme: base.primaryTextTheme.apply( + fontFamily: 'DejaVu Sans', + fontFamilyFallback: _fontFallbacks, + ), + appBarTheme: base.appBarTheme.copyWith( + titleTextStyle: base.appBarTheme.titleTextStyle?.copyWith( + fontFamily: 'DejaVu Sans', + fontFamilyFallback: _fontFallbacks, + ), + ), + ); +} + +/// Wraps [child] as a full screen for capture: the real green theme with +/// screenshot fonts, the given [locale], repositories and an [InventoryCubit]. +/// Pass [textDirection] to force RTL for the layout demo. +Widget screenshotApp({ + required VarietyRepository repository, + required SpeciesRepository species, + required Widget child, + AppLocale locale = AppLocale.en, + TextDirection? textDirection, +}) { + LocaleSettings.setLocaleSync(locale); + return TranslationProvider( + child: MultiRepositoryProvider( + providers: [ + RepositoryProvider.value(value: repository), + RepositoryProvider.value(value: species), + ], + child: BlocProvider( + create: (_) => InventoryCubit(repository), + child: MaterialApp( + debugShowCheckedModeBanner: false, + theme: screenshotTheme(), + locale: materialLocaleFor(locale.flutterLocale), + supportedLocales: AppLocaleUtils.supportedLocales, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + home: textDirection == null + ? child + : Directionality(textDirection: textDirection, child: child), + ), + ), + ), + ); +} + +/// One seeded variety plus the id of a "hero" variety used for the detail shot. +class SeededInventory { + const SeededInventory(this.heroVarietyId); + final String heroVarietyId; +} + +/// Seeds an attractive, internationally-neutral inventory: a spread of +/// colour-coded botanical families, lots with years/quantities/origins, photo +/// avatars, and crop-calendar month masks so the calendar screen is populated. +/// Returns the hero variety (a cherry tomato with several lots) for detail. +Future seedShowcase( + AppDatabase db, + VarietyRepository repo, +) async { + // Sow in spring, harvest seed in late summer — visible in the calendar. + final sow = monthsToMask(const [3, 4, 5]); + final harvest = monthsToMask(const [8, 9]); + + // Varieties render with the app's default coloured-initial disc avatar — + // the on-brand default when no photo is set — so no image decoding is needed. + Future add(String label, String family) async { + final id = await repo.addQuickVariety(label: label, category: family); + await (db.update(db.varieties)..where((v) => v.id.equals(id))).write( + VarietiesCompanion( + sowMonths: Value(sow), + seedHarvestMonths: Value(harvest), + ), + ); + return id; + } + + final tomato = await add('Cherry tomato', 'Solanaceae'); + await repo.addLot( + varietyId: tomato, + harvestYear: 2024, + quantity: const Quantity(kind: QuantityKind.handful), + originName: 'Community seed swap', + abundance: Abundance.plentyToShare, + offerStatus: OfferStatus.shared, + ); + await repo.addLot( + varietyId: tomato, + harvestYear: 2023, + quantity: const Quantity(kind: QuantityKind.packet, count: 1), + offerStatus: OfferStatus.exchange, + ); + + await add('Climbing bean', 'Fabaceae'); + await add('Sunflower', 'Asteraceae'); + await add('Sweet basil', 'Lamiaceae'); + await add('Rainbow chard', 'Amaranthaceae'); + await add('Purple carrot', 'Apiaceae'); + + return SeededInventory(tomato); +} diff --git a/apps/app_seeds/test/screenshots/screenshots_test.dart b/apps/app_seeds/test/screenshots/screenshots_test.dart new file mode 100644 index 0000000..a96ca57 --- /dev/null +++ b/apps/app_seeds/test/screenshots/screenshots_test.dart @@ -0,0 +1,161 @@ +@Tags(['screenshots']) +library; + +import 'dart:async'; + +import 'package:commons_core/commons_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tane/data/species_repository.dart'; +import 'package:tane/data/variety_repository.dart'; +import 'package:tane/db/database.dart'; +import 'package:tane/i18n/strings.g.dart'; +import 'package:tane/state/offers_cubit.dart'; +import 'package:tane/state/variety_detail_cubit.dart'; +import 'package:tane/ui/calendar_screen.dart'; +import 'package:tane/ui/home_screen.dart'; +import 'package:tane/ui/inventory_list_screen.dart'; +import 'package:tane/ui/market_screen.dart'; +import 'package:tane/ui/variety_detail_screen.dart'; + +import '../support/test_support.dart'; +import 'screenshot_support.dart'; + +/// Generates localized marketing/store screenshots as golden PNGs. +/// +/// Run (writes the PNGs) — the `screenshots` tag is skipped by default +/// (dart_test.yaml), so `--run-skipped` is required: +/// flutter test --update-goldens --run-skipped --tags screenshots test/screenshots/screenshots_test.dart +/// +/// Output: test/screenshots/goldens/``/``.png for en/es/fr/de/pt/ja, +/// plus goldens/rtl/ — a right-to-left *layout* demo (English strings mirrored; +/// the app ships no Arabic translation yet, so this proves the design is RTL-safe +/// rather than claiming an Arabic locale). These are raw device-canvas frames +/// with no marketing chrome; add captions/device frames downstream if a store +/// requires them. `tool/collect_screenshots.sh` copies them into site/ and fastlane/. +void main() { + setUpAll(() async { + await loadScreenshotFonts(); + // Touch mode never draws focus rings, so no highlight frames the FAB/fields. + FocusManager.instance.highlightStrategy = + FocusHighlightStrategy.alwaysTouch; + }); + + late AppDatabase db; + late VarietyRepository repo; + late SpeciesRepository species; + late SeededInventory seeded; + + setUp(() async { + db = newTestDatabase(); + repo = newTestRepository(db); + species = newTestSpeciesRepository(db); + seeded = await seedShowcase(db, repo); + }); + tearDown(() => db.close()); + + Future capture(WidgetTester tester, String dir, String name) async { + tester.view.physicalSize = screenshotSize; + tester.view.devicePixelRatio = screenshotPixelRatio; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + await tester.pumpAndSettle(); + await expectLater( + find.byType(MaterialApp), + matchesGoldenFile('goldens/$dir/$name.png'), + ); + await disposeTree(tester); + } + + // A POPULATED market: a seeded OffersCubit (no transport) feeding MarketBody, + // so the offer list renders with no live discovery stream to hang + // pumpAndSettle. (Live discovery is exercised by offers_cubit_test.dart; see + // the note in market_screen_test.dart on why we don't drive it via widgets.) + Future market() async => Builder( + builder: (context) => Scaffold( + appBar: AppBar( + title: Text(context.t.market.title), + actions: [IconButton(icon: const Icon(Icons.tune), onPressed: () {})], + ), + body: BlocProvider( + create: (_) => _SeededOffersCubit(_sampleOffers()), + child: MarketBody(onConfigure: () {}), + ), + ), + ); + + /// Each entry renders one screen; the runner re-seeds and re-locales per shot. + final screens = Function()>{ + 'home': () async => const HomeScreen(marketEnabled: true), + 'inventory': () async => const InventoryListScreen(), + 'market': market, + 'calendar': () async => const CalendarScreen(initialMonth: 4), + 'detail': () async => BlocProvider( + create: (_) => VarietyDetailCubit(repo, seeded.heroVarietyId), + child: const VarietyDetailScreen(), + ), + }; + + for (final locale in screenshotLocales) { + for (final entry in screens.entries) { + testWidgets('${locale.languageCode}/${entry.key}', (tester) async { + await tester.pumpWidget( + screenshotApp( + repository: repo, + species: species, + locale: locale, + child: await entry.value(), + ), + ); + await capture(tester, locale.languageCode, entry.key); + }); + } + } + + // RTL layout demo: base (English) strings, forced right-to-left, so the + // mirrored inventory proves the design is RTL-safe. Not a translated locale. + testWidgets('rtl/inventory', (tester) async { + await tester.pumpWidget( + screenshotApp( + repository: repo, + species: species, + locale: AppLocale.en, + textDirection: TextDirection.rtl, + child: const InventoryListScreen(), + ), + ); + await capture(tester, 'rtl', 'inventory'); + }); +} + +/// An [OffersCubit] pre-loaded with a fixed offer list. It carries a no-op +/// transport (never called — offers are pre-seeded) purely so `isOnline` is +/// true and MarketBody renders the list instead of the "can't reach" state. +class _SeededOffersCubit extends OffersCubit { + _SeededOffersCubit(List offers) : super(_NoopOfferTransport()) { + emit(state.copyWith(offers: offers, hasSearched: true, areaGeohash: 'sp3e')); + } +} + +/// A transport that is present (so the market reads as online) but never used. +class _NoopOfferTransport implements OfferTransport { + @override + Stream discover(DiscoveryQuery query) => const Stream.empty(); + @override + Future publish(Offer offer) => throw UnimplementedError(); + @override + Future retract(String offerId) async {} + @override + Future close() async {} +} + +/// A small, internationally-neutral set of nearby offers across gift / swap / +/// sale and colour-coded botanical families. +List _sampleOffers() => [ + Offer(id: 'o1', authorPubkeyHex: 'a1' * 32, summary: 'Cherry tomato', type: OfferType.gift, category: 'Solanaceae', approxGeohash: 'sp3e9', isOrganic: true), + Offer(id: 'o2', authorPubkeyHex: 'a2' * 32, summary: 'Climbing bean', type: OfferType.exchange, category: 'Fabaceae', approxGeohash: 'sp3e8'), + Offer(id: 'o3', authorPubkeyHex: 'a3' * 32, summary: 'Sunflower', type: OfferType.sale, category: 'Asteraceae', approxGeohash: 'sp3ec', priceAmount: 2, priceCurrency: '€'), + Offer(id: 'o4', authorPubkeyHex: 'a4' * 32, summary: 'Sweet basil', type: OfferType.gift, category: 'Lamiaceae', approxGeohash: 'sp3e2', isOrganic: true), + Offer(id: 'o5', authorPubkeyHex: 'a5' * 32, summary: 'Purple carrot', type: OfferType.exchange, category: 'Apiaceae', approxGeohash: 'sp3ef'), +]; diff --git a/apps/app_seeds/tool/collect_screenshots.sh b/apps/app_seeds/tool/collect_screenshots.sh new file mode 100755 index 0000000..3b887b7 --- /dev/null +++ b/apps/app_seeds/tool/collect_screenshots.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Copies the localized golden screenshots into the two places that consume them: +# 1. the Hugo landing site (site/assets/screenshots//) +# 2. the Play Store metadata (fastlane/metadata/android//images/phoneScreenshots/) +# +# Regenerate the PNGs first: +# flutter test --update-goldens --tags screenshots test/screenshots/screenshots_test.dart +# then run this from apps/app_seeds/: +# tool/collect_screenshots.sh +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # apps/app_seeds +repo_root="$(cd "$here/../.." && pwd)" +goldens="$here/test/screenshots/goldens" +site_dir="$repo_root/site/assets/screenshots" +fastlane_dir="$here/fastlane/metadata/android" + +if [[ ! -d "$goldens" ]]; then + echo "No goldens at $goldens — run the screenshots test with --update-goldens first." >&2 + exit 1 +fi + +# App locale (golden dir) -> Play Store locale (fastlane dir). 'rtl' is a layout +# demo only (not a real locale) and goes to the site, not the stores. +declare -A play_locale=( + [en]=en-US [es]=es-ES [fr]=fr-FR [de]=de-DE [pt]=pt-PT [ja]=ja-JP +) + +for dir in "$goldens"/*/; do + locale="$(basename "$dir")" + + # 1) Landing site: every locale, including the RTL demo. + mkdir -p "$site_dir/$locale" + cp "$dir"*.png "$site_dir/$locale/" + + # 2) Play Store: only locales that ALREADY have text metadata (a title.txt). + # A locale dir with images but no store text is invalid for `fastlane supply`, + # so we don't create image-only locales — add the text first, then it's picked up. + play="${play_locale[$locale]:-}" + if [[ -n "$play" && -f "$fastlane_dir/$play/title.txt" ]]; then + shots="$fastlane_dir/$play/images/phoneScreenshots" + mkdir -p "$shots" + # Number them so the store shows them in a deliberate order. + i=1 + for name in home inventory market calendar detail; do + [[ -f "$dir$name.png" ]] && cp "$dir$name.png" "$shots/$i.png" && i=$((i + 1)) + done + echo " store: $play ($((i - 1)) shots)" + fi + echo "site: $locale" +done + +echo "Done. To add a Play locale beyond en-US/es-ES, create its title/short/full" +echo "description text under fastlane/metadata/android// first, then re-run." diff --git a/docs/que-es-tane.md b/docs/que-es-tane.md index 4d6db46..2eb4e9f 100644 --- a/docs/que-es-tane.md +++ b/docs/que-es-tane.md @@ -1,7 +1,5 @@ # Tane — qué es -> *Texto divulgativo para todos los públicos, sin dar por sabidos los conceptos.* - **Tane** (種) es «semilla» en japonés; el nombre completo, *tanemaki* (種まき), es «sembrar / esparcir semillas». ## En una frase @@ -33,8 +31,6 @@ con libretas y hojas de cálculo. ## Por qué es distinta -*(cada idea, explicada)* - - **Es libre y de todos.** No es de ninguna empresa: cualquiera puede usarla, copiarla, traducirla o mejorarla, gratis y para siempre. Es más una *receta que se comparte* que un producto cerrado — a esto se le llama **software libre**. Y quien la mejore está obligado diff --git a/docs/what-is-tane.md b/docs/what-is-tane.md new file mode 100644 index 0000000..f57170a --- /dev/null +++ b/docs/what-is-tane.md @@ -0,0 +1,72 @@ +# Tane — what it is + +**Tane** (種) means "seed" in Japanese; the full name, *tanemaki* (種まき), means "to sow / scatter seeds". + +## In one sentence + +A simple, free app to **keep your seeds and share them** with whoever you like. It works +**without internet** and with no company in the middle. + +## Why it's needed + +Today a handful of companies control much of the world's seed. Many seeds are made so they +won't serve again: they give one harvest, but their seeds don't germinate well, so you have +to **buy them again every year**. Meanwhile the traditional varieties — grown and improved +over thousands of years — are being lost, and with them the knowledge of how to grow them. + +Sharing seeds is one of the oldest and most natural things there is, yet in some places it +has become complicated or even fined (in France an association was penalised for handing out +varieties that weren't on the official approved list). And the people who care for seeds +still do it with notebooks and spreadsheets. + +## What it does, made easy + +- **Keep** — your seed inventory: what you have, from which year, how much, where it came + from, under **the name you use** (no Latin required). The same whether you have four + packets in a drawer or a group with hundreds of varieties. +- **Share** — you say what you offer; someone nearby sees it and writes to you, to **give + away, swap or sell** if you want. You close the deal yourself, directly. No company steps + in or takes a commission. + +## Why it's different + +- **It's free and belongs to everyone.** It isn't owned by any company: anyone can use, + copy, translate or improve it, free and forever. It's more a *recipe that gets shared* + than a closed product — this is called **free software**. And whoever improves it is + bound to share those improvements just as freely, so no one can "close" it later (that's + **copyleft**). +- **It works offline.** The basics run even out in the field with no signal. +- **Your data is yours.** Your information is kept on **your own phone**, protected with a + key, not on some company's computers. We don't sell it or upload it anywhere. No ads. +- **No one can shut it down or control it.** There's no central computer that everything + passes through: people communicate **directly with each other**, like passing seeds from + hand to hand. So no one can censor it or charge you to use it (that's what being + **decentralized** means). +- **You trust by word of mouth.** You deal with people you know and with those vouched for + by people you trust — the way it's always worked in a village or at a fair — without + having to give your real name or phone number unless you want to. +- **In many languages.** For anywhere in the world, not for a single country. Volunteers + translate it. + +## Who it's for + +Seed groups and networks, gardens and collectives, growers… and anyone with a few packets +put away. For all ages, from 10 to 80. + +## The name + +**Tane** means "seed" (種). It comes from *tanemaki* (種まき), "to sow seeds" in Japanese, a +nod to old customs of mutual aid between neighbours (sharing the work, saving together). + +## Where it stands and how to help + +In its early days. You can help by trying it out in a real seed group, translating it, +sharing what you know about the varieties, or coding. It has no price and never will: it's +sustained by volunteers, by the communities themselves, and by public funding for projects +of common interest. + +--- + +> *The idea underneath: traditional seeds are a heritage of humankind — everyone's and no +> one's. Against those who would privatise them, Tane aims to make it easy for you to keep +> them, multiply them and keep them moving.* diff --git a/site/.dockerignore b/site/.dockerignore new file mode 100644 index 0000000..26c46cb --- /dev/null +++ b/site/.dockerignore @@ -0,0 +1,4 @@ +public/ +resources/ +.hugo_build.lock +.git/ diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..1c3fa73 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,3 @@ +public/ +resources/ +.hugo_build.lock diff --git a/site/Dockerfile b/site/Dockerfile new file mode 100644 index 0000000..6ea3367 --- /dev/null +++ b/site/Dockerfile @@ -0,0 +1,13 @@ +# Multi-stage: build the static site with Hugo, serve it with nginx. +# Build context is site/ only — legal pages are pre-generated (build-legal.sh) +# and committed, and screenshots are copied in by collect_screenshots.sh, so no +# other part of the repo is needed at image-build time. +FROM hugomods/hugo:exts-0.140.2 AS build +WORKDIR /src +COPY . . +RUN hugo --minify --gc + +FROM nginx:alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /src/public /usr/share/nginx/html +EXPOSE 80 diff --git a/site/assets/css/main.css b/site/assets/css/main.css new file mode 100644 index 0000000..4d6614e --- /dev/null +++ b/site/assets/css/main.css @@ -0,0 +1,198 @@ +:root { + --green: #2f7d34; + --green-dark: #22521e; + --appbar: #3a8434; + --canvas: #e7f1e0; + --container: #d3e8c8; + --on-container: #2f6d2a; + --title: #1b2416; + --muted: #5c6b52; + --field: #f3f7ee; + --outline: #c3cbb6; + --white: #fff; + --maxw: 1060px; + --radius: 18px; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", sans-serif; +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } +body { + margin: 0; + color: var(--title); + background: var(--canvas); + line-height: 1.55; + -webkit-font-smoothing: antialiased; +} +img { max-width: 100%; display: block; } +a { color: var(--green); } + +.skip-link { + position: absolute; + inset-inline-start: -999px; + background: var(--green); + color: #fff; + padding: .6rem 1rem; + border-radius: 0 0 8px 0; + z-index: 10; +} +.skip-link:focus { inset-inline-start: 0; } + +/* Header */ +.site-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: .4rem 1.2rem; + padding: .7rem clamp(1rem, 4vw, 2rem); + background: var(--appbar); + color: #fff; + position: sticky; + top: 0; + z-index: 5; +} +.brand { + display: inline-flex; + align-items: center; + gap: .55rem; + color: #fff; + text-decoration: none; + font-weight: 700; + font-size: 1.25rem; + flex-shrink: 0; +} +.brand img { border-radius: 8px; } +.site-nav { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: .5rem 1.1rem; +} +.site-nav a { color: #fff; text-decoration: none; opacity: .95; white-space: nowrap; } +.site-nav a:hover { text-decoration: underline; } +.lang-switch { display: inline-flex; gap: .6rem; font-size: .92rem; white-space: nowrap; } +.lang-switch .current { opacity: .7; } +.lang-switch a.lang { color: #fff; } + +/* Narrow phones: brand on its own row, nav wraps beneath it, left-aligned. */ +@media (max-width: 560px) { + .site-header { gap: .5rem; } + .brand { font-size: 1.15rem; } + .site-nav { width: 100%; gap: .4rem 1rem; font-size: .95rem; } +} + +main { max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(1rem, 4vw, 2rem); } + +/* Hero */ +.hero { + display: grid; + grid-template-columns: 1.1fr .9fr; + gap: clamp(1.5rem, 4vw, 3rem); + align-items: center; + padding: clamp(2rem, 6vw, 4rem) 0 2rem; +} +.hero h1 { + font-size: clamp(2rem, 5vw, 3.1rem); + line-height: 1.1; + margin: 0 0 .6rem; + color: var(--green-dark); +} +.hero .tagline { font-size: 1.25rem; font-weight: 600; color: var(--on-container); margin: 0 0 1rem; } +.hero .lead { font-size: 1.05rem; margin: 0 0 1rem; } +.hero-shot { display: flex; justify-content: center; } + +/* Phone frame */ +.phone { + background: #12261a; + padding: 8px; + border-radius: 28px; + box-shadow: 0 18px 40px rgba(20, 50, 25, .22); + width: 232px; + max-width: 62vw; +} +.phone img { border-radius: 20px; } + +/* Sections */ +section { margin: 2.5rem 0; } +h2 { font-size: clamp(1.4rem, 3vw, 1.9rem); color: var(--green-dark); } + +.pillars { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.2rem; +} +.pillar { + background: var(--white); + border: 1px solid var(--outline); + border-radius: var(--radius); + padding: 1.4rem; +} +.pillar-icon { font-size: 1.8rem; } +.pillar h2 { font-size: 1.2rem; margin: .5rem 0; } +.pillar p { margin: 0; color: #2c3826; } + +.shots .shot-row { + display: flex; + gap: 1.2rem; + justify-content: center; + flex-wrap: wrap; +} +.shots h2 { text-align: center; } + +.values { + background: var(--container); + border-radius: var(--radius); + padding: 1.6rem clamp(1.2rem, 4vw, 2.2rem); +} +.values ul { margin: .5rem 0 0; padding-inline-start: 1.2rem; } +.values li { margin: .45rem 0; } + +.get-it { text-align: center; } +.store-badges { margin-top: 1.2rem; } +.coming-soon { color: var(--muted); font-style: italic; } +.badge { + display: inline-block; + background: var(--green); + color: #fff; + text-decoration: none; + padding: .7rem 1.4rem; + border-radius: 999px; + margin: .3rem; + font-weight: 600; +} + +/* Legal docs */ +.doc { padding: 2rem 0 3rem; max-width: 760px; } +.doc h1 { color: var(--green-dark); } +.doc-body h2 { font-size: 1.3rem; margin-top: 1.8rem; } +.doc-body { font-size: 1.02rem; } +.crumb { color: var(--muted); } +.legal-index { list-style: none; padding: 0; } +.legal-index li { + padding: 1rem 1.2rem; + background: var(--white); + border: 1px solid var(--outline); + border-radius: 12px; + margin: .7rem 0; +} +.legal-index a { font-weight: 600; font-size: 1.1rem; text-decoration: none; } +.legal-index .desc { display: block; color: var(--muted); font-size: .95rem; margin-top: .2rem; } + +/* Footer */ +.site-footer { + background: var(--green-dark); + color: #dfeeda; + margin-top: 3rem; +} +.foot-inner { max-width: var(--maxw); margin: 0 auto; padding: 2rem clamp(1rem, 4vw, 2rem); } +.foot-legal { display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 1rem; } +.foot-legal a { color: #fff; } +.foot-note { margin: .3rem 0; font-size: .92rem; } +.site-footer a { color: #cfe7c3; } + +@media (max-width: 820px) { + .hero { grid-template-columns: 1fr; } + .hero-shot { order: -1; } + .pillars { grid-template-columns: 1fr; } +} diff --git a/site/assets/img/logo.png b/site/assets/img/logo.png new file mode 100644 index 0000000..a2db482 Binary files /dev/null and b/site/assets/img/logo.png differ diff --git a/site/assets/screenshots/de/calendar.png b/site/assets/screenshots/de/calendar.png new file mode 100644 index 0000000..009eb81 Binary files /dev/null and b/site/assets/screenshots/de/calendar.png differ diff --git a/site/assets/screenshots/de/detail.png b/site/assets/screenshots/de/detail.png new file mode 100644 index 0000000..3455f0a Binary files /dev/null and b/site/assets/screenshots/de/detail.png differ diff --git a/site/assets/screenshots/de/home.png b/site/assets/screenshots/de/home.png new file mode 100644 index 0000000..5be2c15 Binary files /dev/null and b/site/assets/screenshots/de/home.png differ diff --git a/site/assets/screenshots/de/inventory.png b/site/assets/screenshots/de/inventory.png new file mode 100644 index 0000000..2533939 Binary files /dev/null and b/site/assets/screenshots/de/inventory.png differ diff --git a/site/assets/screenshots/de/market.png b/site/assets/screenshots/de/market.png new file mode 100644 index 0000000..b6413ef Binary files /dev/null and b/site/assets/screenshots/de/market.png differ diff --git a/site/assets/screenshots/en/calendar.png b/site/assets/screenshots/en/calendar.png new file mode 100644 index 0000000..475e75f Binary files /dev/null and b/site/assets/screenshots/en/calendar.png differ diff --git a/site/assets/screenshots/en/detail.png b/site/assets/screenshots/en/detail.png new file mode 100644 index 0000000..33ee8a4 Binary files /dev/null and b/site/assets/screenshots/en/detail.png differ diff --git a/site/assets/screenshots/en/home.png b/site/assets/screenshots/en/home.png new file mode 100644 index 0000000..820f88f Binary files /dev/null and b/site/assets/screenshots/en/home.png differ diff --git a/site/assets/screenshots/en/inventory.png b/site/assets/screenshots/en/inventory.png new file mode 100644 index 0000000..714a531 Binary files /dev/null and b/site/assets/screenshots/en/inventory.png differ diff --git a/site/assets/screenshots/en/market.png b/site/assets/screenshots/en/market.png new file mode 100644 index 0000000..456b140 Binary files /dev/null and b/site/assets/screenshots/en/market.png differ diff --git a/site/assets/screenshots/es/calendar.png b/site/assets/screenshots/es/calendar.png new file mode 100644 index 0000000..f5387b0 Binary files /dev/null and b/site/assets/screenshots/es/calendar.png differ diff --git a/site/assets/screenshots/es/detail.png b/site/assets/screenshots/es/detail.png new file mode 100644 index 0000000..13eaf36 Binary files /dev/null and b/site/assets/screenshots/es/detail.png differ diff --git a/site/assets/screenshots/es/home.png b/site/assets/screenshots/es/home.png new file mode 100644 index 0000000..f39ef76 Binary files /dev/null and b/site/assets/screenshots/es/home.png differ diff --git a/site/assets/screenshots/es/inventory.png b/site/assets/screenshots/es/inventory.png new file mode 100644 index 0000000..7cd04c5 Binary files /dev/null and b/site/assets/screenshots/es/inventory.png differ diff --git a/site/assets/screenshots/es/market.png b/site/assets/screenshots/es/market.png new file mode 100644 index 0000000..dd12bce Binary files /dev/null and b/site/assets/screenshots/es/market.png differ diff --git a/site/assets/screenshots/fr/calendar.png b/site/assets/screenshots/fr/calendar.png new file mode 100644 index 0000000..24cdb00 Binary files /dev/null and b/site/assets/screenshots/fr/calendar.png differ diff --git a/site/assets/screenshots/fr/detail.png b/site/assets/screenshots/fr/detail.png new file mode 100644 index 0000000..634b1a3 Binary files /dev/null and b/site/assets/screenshots/fr/detail.png differ diff --git a/site/assets/screenshots/fr/home.png b/site/assets/screenshots/fr/home.png new file mode 100644 index 0000000..17a7795 Binary files /dev/null and b/site/assets/screenshots/fr/home.png differ diff --git a/site/assets/screenshots/fr/inventory.png b/site/assets/screenshots/fr/inventory.png new file mode 100644 index 0000000..ba4741c Binary files /dev/null and b/site/assets/screenshots/fr/inventory.png differ diff --git a/site/assets/screenshots/fr/market.png b/site/assets/screenshots/fr/market.png new file mode 100644 index 0000000..346b39d Binary files /dev/null and b/site/assets/screenshots/fr/market.png differ diff --git a/site/assets/screenshots/ja/calendar.png b/site/assets/screenshots/ja/calendar.png new file mode 100644 index 0000000..f833ae1 Binary files /dev/null and b/site/assets/screenshots/ja/calendar.png differ diff --git a/site/assets/screenshots/ja/detail.png b/site/assets/screenshots/ja/detail.png new file mode 100644 index 0000000..33ee8a4 Binary files /dev/null and b/site/assets/screenshots/ja/detail.png differ diff --git a/site/assets/screenshots/ja/home.png b/site/assets/screenshots/ja/home.png new file mode 100644 index 0000000..78b507e Binary files /dev/null and b/site/assets/screenshots/ja/home.png differ diff --git a/site/assets/screenshots/ja/inventory.png b/site/assets/screenshots/ja/inventory.png new file mode 100644 index 0000000..714a531 Binary files /dev/null and b/site/assets/screenshots/ja/inventory.png differ diff --git a/site/assets/screenshots/ja/market.png b/site/assets/screenshots/ja/market.png new file mode 100644 index 0000000..456b140 Binary files /dev/null and b/site/assets/screenshots/ja/market.png differ diff --git a/site/assets/screenshots/pt/calendar.png b/site/assets/screenshots/pt/calendar.png new file mode 100644 index 0000000..e5a5b8d Binary files /dev/null and b/site/assets/screenshots/pt/calendar.png differ diff --git a/site/assets/screenshots/pt/detail.png b/site/assets/screenshots/pt/detail.png new file mode 100644 index 0000000..dfa24bf Binary files /dev/null and b/site/assets/screenshots/pt/detail.png differ diff --git a/site/assets/screenshots/pt/home.png b/site/assets/screenshots/pt/home.png new file mode 100644 index 0000000..c0464e0 Binary files /dev/null and b/site/assets/screenshots/pt/home.png differ diff --git a/site/assets/screenshots/pt/inventory.png b/site/assets/screenshots/pt/inventory.png new file mode 100644 index 0000000..cf033bd Binary files /dev/null and b/site/assets/screenshots/pt/inventory.png differ diff --git a/site/assets/screenshots/pt/market.png b/site/assets/screenshots/pt/market.png new file mode 100644 index 0000000..9e6b437 Binary files /dev/null and b/site/assets/screenshots/pt/market.png differ diff --git a/site/assets/screenshots/rtl/inventory.png b/site/assets/screenshots/rtl/inventory.png new file mode 100644 index 0000000..2ec2b4e Binary files /dev/null and b/site/assets/screenshots/rtl/inventory.png differ diff --git a/site/build-legal.sh b/site/build-legal.sh new file mode 100755 index 0000000..40db16b --- /dev/null +++ b/site/build-legal.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Generates the Hugo legal pages from docs/legal/ — the single source of truth. +# Each doc's first "# Heading" becomes the page title (and is stripped from the +# body so it isn't rendered twice). Run from site/ whenever the legal docs change: +# ./build-legal.sh +# The generated content/legal/..md files are committed so the Docker +# build only needs site/. +set -euo pipefail + +site_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +legal_src="$(cd "$site_dir/../docs/legal" && pwd)" +out="$site_dir/content/legal" + +# slug | English master | Spanish mirror | menu weight +rows=( + "privacy|privacy-policy.md|politica-de-privacidad.md|1" + "terms|terms-of-use.md|condiciones-de-uso.md|2" + "rules|community-rules.md|normas-de-la-comunidad.md|3" + "seeds|seed-legality-notice.md|aviso-sobre-semillas.md|4" +) + +emit() { # + local src="$1" dst="$2" slug="$3" weight="$4" + # First "# ..." line -> title; everything after that line -> body. Strip a + # leading "Tane — " so it doesn't collide with the site title suffix in . + local title + title="$(sed -n 's/^# \(.*\)/\1/p' "$src" | head -n1 \ + | sed -E 's/^Tane[[:space:]]*(—|–|-)[[:space:]]*//')" + { + printf -- '---\n' + printf 'title: "%s"\n' "${title//\"/\\\"}" + printf 'slug: "%s"\n' "$slug" + printf 'weight: %s\n' "$weight" + printf 'translationKey: "legal-%s"\n' "$slug" + printf -- '---\n\n' + # Drop the first H1 line, keep the rest verbatim. + awk 'NR==1 && /^# /{next} {print}' "$src" + } > "$dst" + echo " $(basename "$dst") <- $(basename "$src")" +} + +for row in "${rows[@]}"; do + IFS='|' read -r slug en es weight <<<"$row" + emit "$legal_src/$en" "$out/$slug.en.md" "$slug" "$weight" + emit "$legal_src/es/$es" "$out/$slug.es.md" "$slug" "$weight" +done + +echo "Generated legal pages in $out" + +# --- Plain-language "About" page, generated from the docs/ explainers --------- +# Each page uses its own fixed title (not the doc's H1) and drops the first H1. +docs_dir="$(cd "$site_dir/../docs" && pwd)" +emit_about() { # <src-md> <out-md> <title> <description> + local src="$1" dst="$2" title="$3" desc="$4" + [[ -f "$src" ]] || return 0 + { + printf -- '---\n' + printf 'title: "%s"\n' "$title" + printf 'slug: "about"\n' + printf 'translationKey: "about"\n' + printf 'description: "%s"\n' "$desc" + printf -- '---\n\n' + awk 'NR==1 && /^# /{next} {print}' "$src" + } > "$dst" + echo "Generated $(basename "$dst") <- $(basename "$src")" +} + +emit_about "$docs_dir/que-es-tane.md" "$site_dir/content/about.es.md" \ + "Qué es Tane" "Tane explicada para todos los públicos." +emit_about "$docs_dir/what-is-tane.md" "$site_dir/content/about.en.md" \ + "What is Tane" "Tane explained for everyone." diff --git a/site/config.toml b/site/config.toml new file mode 100644 index 0000000..917ebdd --- /dev/null +++ b/site/config.toml @@ -0,0 +1,36 @@ +baseURL = "https://tane.comunes.org/" +title = "Tane" +defaultContentLanguage = "en" +defaultContentLanguageInSubdir = false +enableRobotsTXT = true +enableGitInfo = false + +# Static, no-JS marketing site. Kept intentionally light: one page + a small +# legal section, English/Spanish. Screenshots come from the app's golden +# harness (apps/app_seeds/test/screenshots), copied in by collect_screenshots.sh. + +[params] + email = "info@comunes.org" + publisher = "Asociación Comunes" + publisherURL = "https://comunes.org" + license = "AGPL-3.0" + # Store links are empty until the app is published; the badges partial renders + # a link only when its URL is set here, so nothing broken shows before launch. + playStoreURL = "" + fdroidURL = "" + +[languages] + [languages.en] + languageName = "English" + languageCode = "en" + weight = 1 + [languages.es] + languageName = "Español" + languageCode = "es" + weight = 2 + +[markup.goldmark.renderer] + unsafe = false + +[minify] + disableJSON = true diff --git a/site/content/_index.en.md b/site/content/_index.en.md new file mode 100644 index 0000000..fdfdf09 --- /dev/null +++ b/site/content/_index.en.md @@ -0,0 +1,34 @@ +--- +title: "Your seed bank in your pocket" +description: "A local-first, decentralized app for keeping and sharing traditional seeds. Offline, encrypted, no account." +hero: + title: "Your seed bank in your pocket" + tagline: "Keep and share traditional seeds — offline, encrypted, no account." + lead: > + Every traditional seed is a letter written by thousands of generations, + passed from hand to hand. Tane helps people and seed-saver collectives keep + a friendly inventory of their bank, decide what they offer, and share it + locally — without a central intermediary that could control it, censor it, + or be fined for it. +pillars: + - icon: "🌱" + title: "Manage your bank" + body: "Add a seed with just a photo and a name — no Latin required. Note the year, how much you have, where it came from, germination tests. Search, filter, snap now and name later." + - icon: "🔒" + title: "Yours, and only yours" + body: "Works fully offline. No account, no server, no trackers. Everything is encrypted on your device. Keep an encrypted backup and a printed recovery sheet to restore your whole bank on a new device." + - icon: "🤝" + title: "Share, as it's always been done" + body: "Mark what you have spare to give away, swap or sell. Someone nearby finds it and contacts you — no middleman. Print a catalog of what you share to take to a seed fair." +values: + title: "Why Tane is different" + points: + - "No center: no company can shut it down, censor it, or be fined for it." + - "Anti-monopoly by design — sharing seeds multiplies the commons instead of depleting it." + - "Spreads like a seed, person to person, with no server in the middle." + - "Free software (AGPL-3.0). No ads, no commissions, no business model." +--- + +Tane (種, "seed"; from *tanemaki* 種まき, "to sow seeds") is a local-first, +decentralized app for keeping and sharing traditional seeds. It works with zero +network — and connects you to a neighborly seed-sharing network when you want it. diff --git a/site/content/_index.es.md b/site/content/_index.es.md new file mode 100644 index 0000000..277faac --- /dev/null +++ b/site/content/_index.es.md @@ -0,0 +1,35 @@ +--- +title: "Tu banco de semillas en el bolsillo" +description: "Una app local-first y descentralizada para guardar y compartir semillas tradicionales. Sin conexión, cifrada, sin cuenta." +hero: + title: "Tu banco de semillas en el bolsillo" + tagline: "Guarda y comparte semillas tradicionales: sin conexión, cifrado, sin cuenta." + lead: > + Cada semilla tradicional es una carta escrita por miles de generaciones, que + pasa de mano en mano. Tane ayuda a personas y colectivos de guardianas de + semillas a llevar un inventario amable de su banco, decidir qué ofrecen y + compartirlo localmente, sin un intermediario central que pueda controlarlo, + censurarlo o ser multado por ello. +pillars: + - icon: "🌱" + title: "Gestiona tu banco" + body: "Añade una semilla con solo una foto y un nombre; sin latín. Anota el año, cuánta tienes, de dónde viene, pruebas de germinación. Busca, filtra, haz la foto ahora y ponle nombre después." + - icon: "🔒" + title: "Tuyo, y solo tuyo" + body: "Funciona totalmente sin conexión. Sin cuenta, sin servidor, sin rastreadores. Todo va cifrado en tu dispositivo. Guarda una copia cifrada y una hoja de recuperación impresa para restaurar todo tu banco en un dispositivo nuevo." + - icon: "🤝" + title: "Compartir, como se ha hecho siempre" + body: "Marca lo que te sobra para regalar, intercambiar o vender. Alguien cerca lo encuentra y te contacta, sin intermediarios. Imprime un catálogo de lo que compartes para llevar a una feria de semillas." +values: + title: "Por qué Tane es diferente" + points: + - "Sin centro: ninguna empresa puede apagarlo, censurarlo ni ser multada por ello." + - "Anti-monopolio por diseño: compartir semillas multiplica el común en lugar de agotarlo." + - "Se propaga como una semilla, de persona a persona, sin servidor en medio." + - "Software libre (AGPL-3.0). Sin anuncios, sin comisiones, sin modelo de negocio." +--- + +Tane (種, "semilla"; de *tanemaki* 種まき, "sembrar semillas") es una app +local-first y descentralizada para guardar y compartir semillas tradicionales. +Funciona sin ninguna red, y te conecta a una red vecinal de intercambio de +semillas cuando tú quieras. diff --git a/site/content/about.en.md b/site/content/about.en.md new file mode 100644 index 0000000..b834ec9 --- /dev/null +++ b/site/content/about.en.md @@ -0,0 +1,78 @@ +--- +title: "What is Tane" +slug: "about" +translationKey: "about" +description: "Tane explained for everyone." +--- + + +**Tane** (種) means "seed" in Japanese; the full name, *tanemaki* (種まき), means "to sow / scatter seeds". + +## In one sentence + +A simple, free app to **keep your seeds and share them** with whoever you like. It works +**without internet** and with no company in the middle. + +## Why it's needed + +Today a handful of companies control much of the world's seed. Many seeds are made so they +won't serve again: they give one harvest, but their seeds don't germinate well, so you have +to **buy them again every year**. Meanwhile the traditional varieties — grown and improved +over thousands of years — are being lost, and with them the knowledge of how to grow them. + +Sharing seeds is one of the oldest and most natural things there is, yet in some places it +has become complicated or even fined (in France an association was penalised for handing out +varieties that weren't on the official approved list). And the people who care for seeds +still do it with notebooks and spreadsheets. + +## What it does, made easy + +- **Keep** — your seed inventory: what you have, from which year, how much, where it came + from, under **the name you use** (no Latin required). The same whether you have four + packets in a drawer or a group with hundreds of varieties. +- **Share** — you say what you offer; someone nearby sees it and writes to you, to **give + away, swap or sell** if you want. You close the deal yourself, directly. No company steps + in or takes a commission. + +## Why it's different + +- **It's free and belongs to everyone.** It isn't owned by any company: anyone can use, + copy, translate or improve it, free and forever. It's more a *recipe that gets shared* + than a closed product — this is called **free software**. And whoever improves it is + bound to share those improvements just as freely, so no one can "close" it later (that's + **copyleft**). +- **It works offline.** The basics run even out in the field with no signal. +- **Your data is yours.** Your information is kept on **your own phone**, protected with a + key, not on some company's computers. We don't sell it or upload it anywhere. No ads. +- **No one can shut it down or control it.** There's no central computer that everything + passes through: people communicate **directly with each other**, like passing seeds from + hand to hand. So no one can censor it or charge you to use it (that's what being + **decentralized** means). +- **You trust by word of mouth.** You deal with people you know and with those vouched for + by people you trust — the way it's always worked in a village or at a fair — without + having to give your real name or phone number unless you want to. +- **In many languages.** For anywhere in the world, not for a single country. Volunteers + translate it. + +## Who it's for + +Seed groups and networks, gardens and collectives, growers… and anyone with a few packets +put away. For all ages, from 10 to 80. + +## The name + +**Tane** means "seed" (種). It comes from *tanemaki* (種まき), "to sow seeds" in Japanese, a +nod to old customs of mutual aid between neighbours (sharing the work, saving together). + +## Where it stands and how to help + +In its early days. You can help by trying it out in a real seed group, translating it, +sharing what you know about the varieties, or coding. It has no price and never will: it's +sustained by volunteers, by the communities themselves, and by public funding for projects +of common interest. + +--- + +> *The idea underneath: traditional seeds are a heritage of humankind — everyone's and no +> one's. Against those who would privatise them, Tane aims to make it easy for you to keep +> them, multiply them and keep them moving.* diff --git a/site/content/about.es.md b/site/content/about.es.md new file mode 100644 index 0000000..55e74ac --- /dev/null +++ b/site/content/about.es.md @@ -0,0 +1,81 @@ +--- +title: "Qué es Tane" +slug: "about" +translationKey: "about" +description: "Tane explicada para todos los públicos." +--- + + +**Tane** (種) es «semilla» en japonés; el nombre completo, *tanemaki* (種まき), es «sembrar / esparcir semillas». + +## En una frase + +Una app sencilla y gratuita para **guardar tus semillas y compartirlas** con quien +quieras. Funciona **sin internet** y sin ninguna empresa de por medio. + +## Por qué hace falta + +Hoy unas pocas empresas controlan gran parte de las semillas del mundo. Muchas están +hechas para no volver a servir: dan una cosecha, pero sus semillas no germinan bien, así +que hay que **comprarlas otra vez cada año**. Mientras tanto, las variedades de toda la +vida —cultivadas y mejoradas durante miles de años— se están perdiendo, y con ellas el +saber cómo cultivarlas. + +Compartir semillas es de lo más viejo y natural que hay, pero en algunos sitios se ha +vuelto complicado o hasta se multa (en Francia sancionaron a una asociación por repartir +variedades que no estaban en la lista oficial permitida). Y quien las cuida lo hace hoy +con libretas y hojas de cálculo. + +## Qué hace, y fácil + +- **Guardar** — tu inventario de semillas: qué tienes, de qué año, cuánta, de dónde vino, + con **el nombre que tú usas** (no hace falta saber latín). Igual para quien tiene cuatro + sobres en un cajón que para un grupo con cientos de variedades. +- **Compartir** — dices qué ofreces; alguien cerca lo ve y te escribe, para **regalar, + intercambiar o vender** si quieres. El trato lo cierras tú, directamente. Ninguna empresa + se mete ni se lleva comisión. + +## Por qué es distinta + +- **Es libre y de todos.** No es de ninguna empresa: cualquiera puede usarla, copiarla, + traducirla o mejorarla, gratis y para siempre. Es más una *receta que se comparte* que un + producto cerrado — a esto se le llama **software libre**. Y quien la mejore está obligado + a compartir esas mejoras igual de libres, para que nadie pueda «cerrarla» después (eso es + el **copyleft**). +- **Funciona sin internet.** Lo básico va aunque estés en el campo sin cobertura. +- **Tus datos son tuyos.** Tu información se guarda en **tu propio móvil**, protegida con una + clave, no en los ordenadores de una empresa. No la vendemos ni la subimos a ningún sitio. + Sin publicidad. +- **Nadie la puede apagar ni controlar.** No hay un ordenador central por el que pase todo: + las personas se comunican **directamente entre sí**, como quien se pasa unas semillas de + mano en mano. Por eso nadie puede censurarla ni cobrarte por usarla (eso es que sea + **descentralizada**). +- **Te fías por el boca a boca.** Te relacionas con gente que conoces y con quien te + recomienda gente de confianza —como siempre se ha hecho en un pueblo o en una feria— sin + tener que dar tu nombre real ni tu teléfono si no quieres. +- **En muchos idiomas.** Para cualquier lugar del mundo, no para un solo país. La traduce + gente voluntaria. + +## Para quién + +Grupos y redes de semillas, huertos y colectivos, agricultores y agricultoras… y cualquier +persona con unos sobres guardados. Para todas las edades, de 10 a 80. + +## El nombre + +**Tane** quiere decir «semilla» (種). Viene de *tanemaki* (種まき), «sembrar semillas» en +japonés, un guiño a viejas costumbres de ayuda mutua entre vecinos (compartir el trabajo, +ahorrar juntos). + +## En qué punto está y cómo ayudar + +En sus primeros pasos. Se puede ayudar probándola en un grupo de semillas de verdad, +traduciéndola, aportando lo que sabes de las variedades, o programando. No tiene ni tendrá +precio: se sostiene con gente voluntaria, con las propias comunidades y con ayudas públicas +a proyectos de interés común. + +--- + +> *Idea de fondo: las semillas de toda la vida son un patrimonio de la humanidad —de todos +> y de nadie—. Frente a quien quiere privatizarlas, Tane busca ponértelo fácil para +> guardarlas, multiplicarlas y hacerlas circular.* diff --git a/site/content/legal/_index.en.md b/site/content/legal/_index.en.md new file mode 100644 index 0000000..8742ff6 --- /dev/null +++ b/site/content/legal/_index.en.md @@ -0,0 +1,4 @@ +--- +title: "Legal" +description: "Tane's privacy policy, terms of use, community rules and seed-legality notice." +--- diff --git a/site/content/legal/_index.es.md b/site/content/legal/_index.es.md new file mode 100644 index 0000000..dda2e3e --- /dev/null +++ b/site/content/legal/_index.es.md @@ -0,0 +1,4 @@ +--- +title: "Legal" +description: "Política de privacidad, condiciones de uso, normas de la comunidad y aviso sobre semillas de Tane." +--- diff --git a/site/content/legal/privacy.en.md b/site/content/legal/privacy.en.md new file mode 100644 index 0000000..5c21aa7 --- /dev/null +++ b/site/content/legal/privacy.en.md @@ -0,0 +1,96 @@ +--- +title: "Privacy Policy" +slug: "privacy" +weight: 1 +translationKey: "legal-privacy" +--- + + +**Version 1.0 — 13 July 2026** + +Tane is published by **Asociación Comunes** (Spain) — <https://comunes.org> — +contact: <info@comunes.org>. Tane is free software (AGPL-3.0); its source code is +public, so everything described here can be verified. + +## The short version + +- Tane works **without an account**. We don't know who you are. +- Everything you record stays **on your device, encrypted**. We run no servers that + receive it, and we have no way to read it. +- Nothing leaves your device unless **you** choose to share it (an offer, a message, + your profile). What you share travels through community-run servers we don't control. +- **No analytics, no ads, no trackers.** Tane collects nothing about you. + +## 1. No accounts, no collection by us + +Tane does not ask for your name, email, or phone number. There is no registration and +no central Tane server. Your identity in the app is a cryptographic key created on your +device and stored in your device's secure keystore. Asociación Comunes does not collect, +receive, or process your personal data through the app. + +## 2. Data stored on your device + +Your seed inventory (varieties, quantities, notes, photos), your messages, your +contacts' vouches and ratings, and your keys are stored **only on your device**, in an +encrypted database (SQLCipher). The encryption key lives in your device's system +keystore. Backups you create are also encrypted; you choose where to keep them, and +they never pass through us. + +## 3. Data that leaves your device — only when you choose + +Nothing is published automatically. Each of these happens only by your explicit action: + +- **Offers.** When you offer seeds, the app publishes the offer's title, description, + optional photo, optional price, and an **approximate area** (a zone of roughly 2–80 km, + as you configure it — never your address or precise location). Offers are public. +- **Profile.** If you fill it in, your chosen name, bio, and avatar are public. +- **Messages.** Direct messages are **end-to-end encrypted**: only you and the person + you write to can read them. The servers that carry them see only encrypted envelopes. +- **Vouches and ratings.** If you vouch for or rate someone, that statement is public + and signed by your key. +- **Sync between your devices.** If you link a second device, your data travels between + them encrypted so that only your devices can read it. + +## 4. Where shared data goes: community relays + +Tane's social features use open, community-run servers ("relays"). By default the app +uses a small set that includes `relay.comunes.org` (operated by Asociación Comunes) and +well-known public relays; you can change this list, or empty it to turn the network off +entirely. Relays other than `relay.comunes.org` are **third-party infrastructure**: +their operators decide their own retention and policies, and this policy does not cover +them. + +## 5. Deletion — an honest note + +You can delete anything local instantly, and you can withdraw an offer at any time. +When you withdraw or delete something you had published, the app asks the relays to +delete it too. Relays we don't operate may keep copies despite that request, and public +posts may have been copied elsewhere while they were visible. **Treat anything you +publish as potentially permanent.** On `relay.comunes.org` we honour deletion requests. + +## 6. Device permissions + +- **Photos / camera** — only when you attach a picture to a variety, offer, or profile. +- **Approximate location** (optional) — only if you use "set my area from where I am", + to pick your coarse sharing zone. Tane never requests precise location. +- **Notifications** (optional) — to tell you about new messages. + +## 7. Your rights + +Because your data lives on your device under your control, you exercise most rights +yourself: read, correct, export, or delete everything from within the app. For data on +`relay.comunes.org`, or any question about this policy, write to <info@comunes.org>. +If you are in the EU, you also have the right to complain to your data protection +authority (in Spain, the AEPD). + +## 8. Children + +The seed inventory can be used by anyone. The market and messaging features are not +directed at children; by using them you confirm you are old enough to use social +features under the laws of your country. + +## 9. Changes + +We will update this policy if Tane's behaviour changes, and note the changes in the +app's release notes. The current version always lives at +<https://tane.comunes.org/legal/privacy>, with history in the public repository. diff --git a/site/content/legal/privacy.es.md b/site/content/legal/privacy.es.md new file mode 100644 index 0000000..3e89154 --- /dev/null +++ b/site/content/legal/privacy.es.md @@ -0,0 +1,102 @@ +--- +title: "Política de privacidad" +slug: "privacy" +weight: 1 +translationKey: "legal-privacy" +--- + + +**Versión 1.0 — 13 de julio de 2026** + +Tane está publicada por la **Asociación Comunes** (España) — <https://comunes.org> — +contacto: <info@comunes.org>. Tane es software libre (AGPL-3.0); su código fuente es +público, así que todo lo que se describe aquí puede verificarse. + +## La versión corta + +- Tane funciona **sin cuenta**. No sabemos quién eres. +- Todo lo que registras se queda **en tu dispositivo, cifrado**. No tenemos servidores + que lo reciban ni forma de leerlo. +- Nada sale de tu dispositivo salvo que **tú** decidas compartirlo (una oferta, un + mensaje, tu perfil). Lo que compartes viaja por servidores comunitarios que no + controlamos. +- **Sin analíticas, sin publicidad, sin rastreadores.** Tane no recoge nada sobre ti. + +## 1. Sin cuentas, sin recogida por nuestra parte + +Tane no pide tu nombre, correo ni teléfono. No hay registro ni servidor central de +Tane. Tu identidad en la aplicación es una clave criptográfica creada en tu dispositivo +y guardada en el almacén seguro del sistema. La Asociación Comunes no recoge, recibe ni +trata tus datos personales a través de la aplicación. + +## 2. Datos guardados en tu dispositivo + +Tu inventario de semillas (variedades, cantidades, notas, fotos), tus mensajes, los +avales y valoraciones de tus contactos y tus claves se guardan **solo en tu +dispositivo**, en una base de datos cifrada (SQLCipher). La clave de cifrado vive en el +almacén seguro del sistema. Las copias de seguridad que crees también van cifradas; tú +eliges dónde guardarlas y nunca pasan por nosotros. + +## 3. Datos que salen de tu dispositivo — solo cuando tú quieres + +Nada se publica automáticamente. Cada una de estas cosas ocurre solo por tu acción +explícita: + +- **Ofertas.** Cuando ofreces semillas, la aplicación publica el título, la + descripción, una foto opcional, un precio opcional y una **zona aproximada** (un área + de entre 2 y 80 km, según la configures — nunca tu dirección ni tu ubicación + precisa). Las ofertas son públicas. +- **Perfil.** Si lo rellenas, el nombre que elijas, tu biografía y tu avatar son + públicos. +- **Mensajes.** Los mensajes directos van **cifrados de extremo a extremo**: solo tú y + la persona a la que escribes podéis leerlos. Los servidores que los transportan solo + ven sobres cifrados. +- **Avales y valoraciones.** Si avalas o valoras a alguien, esa declaración es pública + y va firmada con tu clave. +- **Sincronización entre tus dispositivos.** Si enlazas un segundo dispositivo, tus + datos viajan entre ellos cifrados de forma que solo tus dispositivos pueden leerlos. + +## 4. A dónde van los datos compartidos: servidores comunitarios + +Las funciones sociales de Tane usan servidores abiertos gestionados por comunidades +("relés"). Por defecto la aplicación usa un pequeño conjunto que incluye +`relay.comunes.org` (operado por la Asociación Comunes) y relés públicos conocidos; +puedes cambiar esa lista, o vaciarla para desconectar la red por completo. Los relés +distintos de `relay.comunes.org` son **infraestructura de terceros**: sus operadores +deciden su propia retención y sus políticas, y esta política no los cubre. + +## 5. Borrado — una nota honesta + +Puedes borrar al instante todo lo local, y puedes retirar una oferta en cualquier +momento. Cuando retiras o borras algo que habías publicado, la aplicación pide a los +relés que también lo borren. Los relés que no operamos pueden conservar copias pese a +esa petición, y lo publicado pudo copiarse a otros sitios mientras fue visible. +**Trata todo lo que publiques como potencialmente permanente.** En `relay.comunes.org` +atendemos las peticiones de borrado. + +## 6. Permisos del dispositivo + +- **Fotos / cámara** — solo cuando adjuntas una imagen a una variedad, oferta o perfil. +- **Ubicación aproximada** (opcional) — solo si usas "fijar mi zona desde donde estoy", + para elegir tu zona amplia de intercambio. Tane nunca pide la ubicación precisa. +- **Notificaciones** (opcional) — para avisarte de mensajes nuevos. + +## 7. Tus derechos + +Como tus datos viven en tu dispositivo y bajo tu control, la mayoría de derechos los +ejerces tú directamente: consultar, corregir, exportar o borrar todo desde la propia +aplicación. Para los datos en `relay.comunes.org`, o cualquier duda sobre esta +política, escribe a <info@comunes.org>. Si estás en la UE, también tienes derecho a +reclamar ante tu autoridad de protección de datos (en España, la AEPD). + +## 8. Menores + +El inventario de semillas puede usarlo cualquiera. El mercado y la mensajería no están +dirigidos a menores; al usarlos confirmas que tienes edad suficiente para usar +funciones sociales según las leyes de tu país. + +## 9. Cambios + +Actualizaremos esta política si cambia el comportamiento de Tane, y lo señalaremos en +las notas de cada versión. La versión vigente está siempre en +<https://tane.comunes.org/legal/privacy>, con su historial en el repositorio público. diff --git a/site/content/legal/rules.en.md b/site/content/legal/rules.en.md new file mode 100644 index 0000000..7261fed --- /dev/null +++ b/site/content/legal/rules.en.md @@ -0,0 +1,47 @@ +--- +title: "Community Rules" +slug: "rules" +weight: 3 +translationKey: "legal-rules" +--- + + +**Version 1.0 — 13 July 2026** + +The market and messages in Tane are shared spaces built on trust between neighbours. +These rules are what everyone accepts before joining. They also define what counts as +content that can be reported and removed from the servers we operate. + +## The rules + +1. **Be honest about your seeds.** Describe what you actually have: the variety as you + know it, the harvest year, anything relevant about how it was grown. Don't invent + provenance. +2. **Share only what you may share.** Don't offer: + - seed of **commercially protected varieties** (plant variety rights, patents) + unless you are authorized; + - **invasive or protected species** where their exchange is prohibited; + - anything else that is illegal to share or sell where you or the recipient live. +3. **Seeds, not anything else.** The market is for seeds and plant reproductive + material in the spirit of the app. It is not a general classifieds board. +4. **Respect people.** No harassment, threats, hate, or discrimination — in offers, + profiles, ratings, or messages. +5. **No spam or scams.** No repeated postings, misleading offers, phishing, or + pressure to move people onto shady deals. +6. **Honest reputation.** Vouch only for people you have actually met or exchanged + with; rate only real experiences. Don't game the trust system. + +## What happens if someone breaks them + +- Anyone can **block** you — your offers and messages disappear for them. +- Anyone can **report** an offer or a person. Reports travel to the servers that carry + the content; on the relay operated by Comunes (`relay.comunes.org`), content and keys + that break these rules are removed. Other servers apply their own policies. +- There are no appeals or account suspensions because there are no accounts: the + network simply stops carrying and showing what breaks the rules. + +## A note on good faith + +Most seed sharing is between amateurs, in small quantities, for the joy of keeping +varieties alive. When in doubt, prefer **gifting and swapping** over selling, label +things clearly, and ask. That's how it's always been done. diff --git a/site/content/legal/rules.es.md b/site/content/legal/rules.es.md new file mode 100644 index 0000000..ec33f95 --- /dev/null +++ b/site/content/legal/rules.es.md @@ -0,0 +1,54 @@ +--- +title: "Normas de la comunidad" +slug: "rules" +weight: 3 +translationKey: "legal-rules" +--- + + +**Versión 1.0 — 13 de julio de 2026** + +El mercado y los mensajes de Tane son espacios compartidos construidos sobre la +confianza entre vecinas y vecinos. Estas normas son lo que todo el mundo acepta antes +de entrar. También definen qué contenido puede denunciarse y eliminarse de los +servidores que operamos. + +## Las normas + +1. **Sé honesto con tus semillas.** Describe lo que realmente tienes: la variedad tal + y como la conoces, el año de cosecha, lo relevante sobre cómo se cultivó. No + inventes procedencias. +2. **Comparte solo lo que puedas compartir.** No ofrezcas: + - semilla de **variedades protegidas comercialmente** (obtenciones vegetales, + patentes) salvo que tengas autorización; + - **especies invasoras o protegidas** allí donde su intercambio esté prohibido; + - cualquier otra cosa que sea ilegal compartir o vender donde vives tú o donde vive + quien la recibe. +3. **Semillas, no cualquier cosa.** El mercado es para semillas y material de + reproducción vegetal, en el espíritu de la aplicación. No es un tablón de anuncios + general. +4. **Respeta a las personas.** Nada de acoso, amenazas, odio o discriminación — ni en + ofertas, ni en perfiles, ni en valoraciones, ni en mensajes. +5. **Sin spam ni engaños.** Nada de publicaciones repetidas, ofertas engañosas, + phishing o presiones para llevar a la gente a tratos turbios. +6. **Reputación honesta.** Avala solo a personas que conozcas de verdad o con las que + hayas intercambiado; valora solo experiencias reales. No hagas trampas al sistema de + confianza. + +## Qué pasa si alguien las incumple + +- Cualquiera puede **bloquearte** — tus ofertas y mensajes desaparecen para esa + persona. +- Cualquiera puede **denunciar** una oferta o a una persona. Las denuncias llegan a los + servidores que transportan el contenido; en el relé operado por Comunes + (`relay.comunes.org`), el contenido y las claves que incumplen estas normas se + eliminan. Otros servidores aplican sus propias políticas. +- No hay apelaciones ni suspensiones de cuenta porque no hay cuentas: sencillamente, la + red deja de transportar y de mostrar lo que incumple las normas. + +## Una nota sobre la buena fe + +La mayor parte del intercambio de semillas ocurre entre aficionados, en pequeñas +cantidades, por el gusto de mantener vivas las variedades. Ante la duda, prefiere +**regalar e intercambiar** antes que vender, etiqueta las cosas con claridad y +pregunta. Así se ha hecho siempre. diff --git a/site/content/legal/seeds.en.md b/site/content/legal/seeds.en.md new file mode 100644 index 0000000..b7269e1 --- /dev/null +++ b/site/content/legal/seeds.en.md @@ -0,0 +1,66 @@ +--- +title: "Notice on the Legality of Exchanging Seeds" +slug: "seeds" +weight: 4 +translationKey: "legal-seeds" +--- + + +**Version 1.0 — 13 July 2026** + +Tane helps people keep and share traditional seeds. Laws about seeds differ a lot +between countries, and they distinguish sharply between **giving or swapping** seeds +and **selling** them. This notice is general information, not legal advice; you are +responsible for following the law where you live and where your seeds are going. + +## Gifting and swapping between amateurs + +Exchanging seeds **without commercial purpose** — gifts, swaps, seed libraries, +conservation networks — is broadly recognized and lawful in most places: + +- **European Union.** EU seed marketing law regulates *commercial marketing* of seed. + Exchange in kind between individuals, and the work of conservation networks and gene + banks, fall outside or are expressly exempted; the plant-reproductive-material reform + underway (proposal COM(2023) 414) keeps exemptions for amateurs and conservation + networks, provided the activity is not commercial. +- **Spain.** Ley 30/2006 regulates the *commercial* seed trade; non-commercial exchange + between amateurs and conservation-oriented exchange are recognized practice (art. 24). +- **United States.** Following the 2016 amendment to the Recommended Uniform State Seed + Law, most states exempt **non-commercial seed sharing** (seed libraries, swaps) from + commercial labeling and testing requirements. + +## Selling seeds + +Selling is different. In the EU and many other jurisdictions, **marketing seed of +varieties not registered in an official catalogue may be restricted or prohibited**, +and quantities, labeling, or species may be regulated even for small sellers. +Exemptions for conservation varieties and amateur quantities exist but vary. If you +use Tane's option to ask a price, **check your national rules first** — the app cannot +do this for you. + +## Protected varieties + +Varieties covered by **plant variety rights (PVR/PBR) or patents** may not be +propagated or sold without the holder's authorization — this can apply even to seed +you bought legally. Traditional and heirloom varieties are generally not protected, +but modern commercial varieties usually are. When in doubt, don't offer it. + +## Sending seeds across borders + +Most countries restrict importing seeds: **phytosanitary certificates**, prohibited +species lists, and quarantine rules commonly apply — even to small envelopes between +individuals, and also **within** the EU for certain species (plant passport rules). +Before mailing seeds to another country, check the rules of the destination country. +The safest exchanges are local ones — which is what Tane is designed for. + +## Invasive and protected species + +Some species may not be exchanged at all in some regions, either because they are +**invasive** there or because they are **protected**. Lists are regional; check yours. + +--- + +*Tane takes no commission, mediates no transaction, and verifies no offer: exchanges +are private dealings between the people involved (see the +[Terms of use](terms-of-use.md)). This notice exists so those dealings are informed +ones.* diff --git a/site/content/legal/seeds.es.md b/site/content/legal/seeds.es.md new file mode 100644 index 0000000..3724d12 --- /dev/null +++ b/site/content/legal/seeds.es.md @@ -0,0 +1,72 @@ +--- +title: "Aviso sobre la legalidad del intercambio de semillas" +slug: "seeds" +weight: 4 +translationKey: "legal-seeds" +--- + + +**Versión 1.0 — 13 de julio de 2026** + +Tane ayuda a conservar y compartir semillas tradicionales. Las leyes sobre semillas +varían mucho entre países, y distinguen con claridad entre **regalar o intercambiar** +semillas y **venderlas**. Este aviso es información general, no asesoramiento jurídico; +eres responsable de cumplir la ley allí donde vives y allí donde van tus semillas. + +## Regalar e intercambiar entre aficionados + +Intercambiar semillas **sin ánimo comercial** — regalos, trueques, bibliotecas de +semillas, redes de conservación — está ampliamente reconocido y es legal en la mayoría +de lugares: + +- **Unión Europea.** La normativa de semillas de la UE regula la *comercialización* de + semilla. El intercambio en especie entre particulares y la labor de las redes de + conservación y bancos de germoplasma quedan fuera o están expresamente exentos; la + reforma en curso del material de reproducción vegetal (propuesta COM(2023) 414) + mantiene exenciones para aficionados y redes de conservación, siempre que la + actividad no sea comercial. +- **España.** La Ley 30/2006 regula el comercio de semillas; el intercambio no + comercial entre aficionados y el intercambio con fines de conservación son práctica + reconocida (art. 24). +- **Estados Unidos.** Tras la enmienda de 2016 a la ley uniforme estatal de semillas + (RUSSL), la mayoría de estados eximen el **intercambio no comercial** (bibliotecas de + semillas, trueques) de los requisitos comerciales de etiquetado y análisis. + +## Vender semillas + +Vender es distinto. En la UE y en muchas otras jurisdicciones, **comercializar semilla +de variedades no inscritas en un catálogo oficial puede estar restringido o +prohibido**, y las cantidades, el etiquetado o las especies pueden estar regulados +incluso para vendedores pequeños. Existen exenciones para variedades de conservación y +cantidades de aficionado, pero varían. Si usas la opción de Tane de pedir un precio, +**consulta antes tu normativa nacional** — la aplicación no puede hacerlo por ti. + +## Variedades protegidas + +Las variedades cubiertas por **títulos de obtención vegetal (PVR/PBR) o patentes** no +pueden propagarse ni venderse sin autorización del titular — y esto puede aplicarse +incluso a semilla comprada legalmente. Las variedades tradicionales y de herencia +normalmente no están protegidas; las variedades comerciales modernas casi siempre sí. +Ante la duda, no la ofrezcas. + +## Enviar semillas entre países + +La mayoría de países restringen la importación de semillas: **certificados +fitosanitarios**, listas de especies prohibidas y normas de cuarentena se aplican con +frecuencia — incluso a sobres pequeños entre particulares, y también **dentro** de la +UE para ciertas especies (pasaporte fitosanitario). Antes de enviar semillas a otro +país, consulta las normas del país de destino. Los intercambios más seguros son los +locales — que es justo para lo que está pensada Tane. + +## Especies invasoras y protegidas + +Algunas especies no pueden intercambiarse en absoluto en ciertas regiones, bien porque +allí son **invasoras**, bien porque están **protegidas**. Las listas son regionales; +consulta la tuya. + +--- + +*Tane no cobra comisiones, no media en transacciones y no verifica ofertas: los +intercambios son tratos privados entre las personas implicadas (ver las +[Condiciones de uso](condiciones-de-uso.md)). Este aviso existe para que esos tratos +sean informados.* diff --git a/site/content/legal/terms.en.md b/site/content/legal/terms.en.md new file mode 100644 index 0000000..faf6a69 --- /dev/null +++ b/site/content/legal/terms.en.md @@ -0,0 +1,81 @@ +--- +title: "Terms of Use" +slug: "terms" +weight: 2 +translationKey: "legal-terms" +--- + + +**Version 1.0 — 13 July 2026** + +These terms cover the use of the Tane application, published by **Asociación Comunes** +(Spain) — <https://comunes.org>, contact <info@comunes.org>. By using Tane's sharing +features (the market and messaging) you accept these terms and the +[Community rules](community-rules.md). + +## 1. What Tane is — and what it is not + +Tane is a **personal tool**: a seed inventory that lives on your device, plus a way to +discover people near you who share seeds. Tane is **not a marketplace operator, not a +shop, and not a party to any exchange**: + +- We host no listings — offers are published by you, under your own key, to + community-run relays. +- We take **no commission** and process no payments. Any exchange, gift, or sale is + agreed and carried out directly between the people involved. +- We do not verify seeds, users, or offers, and we cannot remove content from servers + we do not operate. + +## 2. Your responsibilities + +You are solely responsible for: + +- **What you offer and exchange.** Make sure you are allowed to share or sell the + seeds you list. Rules differ by country — see the + [Seed legality notice](seed-legality-notice.md). In particular, selling seed of + varieties that are not registered may be restricted where you live, and propagating + commercially protected varieties without authorization is illegal in most countries. +- **Shipping.** Sending seeds across borders is often restricted (phytosanitary rules). + Check before you mail anything abroad. +- **What you publish.** Offers, your profile, vouches, and ratings are public and + signed by you. Follow the [Community rules](community-rules.md). +- **Your keys and backups.** There is no account recovery: if you lose your device and + your recovery code, we cannot restore anything. + +## 3. No warranties + +Tane is provided **"as is"**, without warranty of any kind, as free software under the +[AGPL-3.0 license](https://www.gnu.org/licenses/agpl-3.0.html). In particular, nobody — +not Comunes, not the people you exchange with through the app — guarantees the +**identity, purity, germination, health, or legality** of any seed exchanged. Growing +information shown in the app (calendars, seed-saving tips, viability estimates) is +general guidance, not professional advice. + +## 4. Disputes between users + +Exchanges are private dealings between the people involved. Comunes is not a mediator +and accepts no liability for failed exchanges, quality disputes, or losses arising from +them. The vouching and rating features exist so communities can build their own trust. + +## 5. Moderation + +There is no central moderation: your app filters what you see through the people you +trust, and you can **block** anyone and **report** offers or people from within the +app. Reports are shared with the servers involved; on the relay we operate +(`relay.comunes.org`), content that breaks the community rules is removed. Relays run +by others apply their own policies. + +## 6. Liability + +To the maximum extent permitted by law, Asociación Comunes' total liability arising +from the app is limited to the amount you paid for it (Tane is gratis: zero). Nothing +in these terms limits liability that cannot be limited by law, or your statutory rights +as a consumer. + +## 7. General + +If a part of these terms is found invalid, the rest remains in force. These terms are +governed by Spanish and EU law, without depriving you of protections of the law of +your country of residence. We may update these terms; material changes will be noted in +the app's release notes, and the current version always lives at +<https://tane.comunes.org/legal/terms>. diff --git a/site/content/legal/terms.es.md b/site/content/legal/terms.es.md new file mode 100644 index 0000000..e3c11d6 --- /dev/null +++ b/site/content/legal/terms.es.md @@ -0,0 +1,84 @@ +--- +title: "Condiciones de uso" +slug: "terms" +weight: 2 +translationKey: "legal-terms" +--- + + +**Versión 1.0 — 13 de julio de 2026** + +Estas condiciones regulan el uso de la aplicación Tane, publicada por la **Asociación +Comunes** (España) — <https://comunes.org>, contacto <info@comunes.org>. Al usar las +funciones de intercambio de Tane (el mercado y la mensajería) aceptas estas condiciones +y las [Normas de la comunidad](normas-de-la-comunidad.md). + +## 1. Qué es Tane — y qué no es + +Tane es una **herramienta personal**: un inventario de semillas que vive en tu +dispositivo, más una forma de descubrir personas cercanas que comparten semillas. Tane +**no es un operador de mercado, ni una tienda, ni parte de ningún intercambio**: + +- No alojamos anuncios — las ofertas las publicas tú, con tu propia clave, en relés + gestionados por comunidades. +- No cobramos **ninguna comisión** ni procesamos pagos. Todo intercambio, regalo o + venta se acuerda y se realiza directamente entre las personas implicadas. +- No verificamos semillas, personas ni ofertas, y no podemos eliminar contenido de + servidores que no operamos. + +## 2. Tus responsabilidades + +Eres la única persona responsable de: + +- **Lo que ofreces e intercambias.** Asegúrate de que puedes compartir o vender las + semillas que publicas. Las normas cambian según el país — mira el + [Aviso sobre semillas](aviso-sobre-semillas.md). En particular, vender semilla de + variedades no registradas puede estar restringido donde vives, y propagar variedades + protegidas comercialmente sin autorización es ilegal en la mayoría de países. +- **Los envíos.** Enviar semillas entre países suele estar restringido (normas + fitosanitarias). Infórmate antes de mandar nada al extranjero. +- **Lo que publicas.** Las ofertas, tu perfil, los avales y las valoraciones son + públicos y van firmados por ti. Sigue las + [Normas de la comunidad](normas-de-la-comunidad.md). +- **Tus claves y copias de seguridad.** No hay recuperación de cuenta: si pierdes tu + dispositivo y tu código de recuperación, no podemos restaurar nada. + +## 3. Sin garantías + +Tane se ofrece **"tal cual"**, sin garantía de ningún tipo, como software libre bajo la +[licencia AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html). En particular, nadie — +ni Comunes, ni las personas con las que intercambias a través de la aplicación — +garantiza la **identidad, pureza, germinación, sanidad o legalidad** de ninguna semilla +intercambiada. La información de cultivo que muestra la aplicación (calendarios, +consejos para extraer semilla, estimaciones de viabilidad) es orientación general, no +asesoramiento profesional. + +## 4. Conflictos entre personas usuarias + +Los intercambios son tratos privados entre las personas implicadas. Comunes no actúa +como mediadora y no acepta responsabilidad por intercambios fallidos, disputas de +calidad o pérdidas derivadas de ellos. Los avales y las valoraciones existen para que +cada comunidad construya su propia confianza. + +## 5. Moderación + +No hay moderación central: tu aplicación filtra lo que ves a través de la gente en la +que confías, y puedes **bloquear** a cualquiera y **denunciar** ofertas o personas +desde la propia aplicación. Las denuncias se comparten con los servidores implicados; +en el relé que operamos (`relay.comunes.org`), el contenido que incumple las normas de +la comunidad se elimina. Los relés de terceros aplican sus propias políticas. + +## 6. Responsabilidad + +En la máxima medida que permita la ley, la responsabilidad total de la Asociación +Comunes derivada de la aplicación se limita a lo que pagaste por ella (Tane es +gratuita: cero). Nada en estas condiciones limita la responsabilidad que no pueda +limitarse por ley, ni tus derechos como consumidor. + +## 7. General + +Si una parte de estas condiciones resultara inválida, el resto sigue en vigor. Estas +condiciones se rigen por el derecho español y de la UE, sin privarte de las +protecciones de la ley de tu país de residencia. Podemos actualizar estas condiciones; +los cambios relevantes se señalarán en las notas de versión de la aplicación, y la +versión vigente está siempre en <https://tane.comunes.org/legal/terms>. diff --git a/site/i18n/en.toml b/site/i18n/en.toml new file mode 100644 index 0000000..1293a73 --- /dev/null +++ b/site/i18n/en.toml @@ -0,0 +1,35 @@ +[skipToContent] +other = "Skip to content" + +[navAbout] +other = "About" + +[navLegal] +other = "Legal" + +[navHome] +other = "Home" + +[screenshotsTitle] +other = "A look inside" + +[getItTitle] +other = "Get Tane" + +[getItComingSoon] +other = "Publishing to app stores soon. Meanwhile, Tane is free software you can build and run today." + +[legalIntro] +other = "The plain-language documents that govern Tane. Short version: your data stays on your device, Tane is only a tool, and you are responsible for the seeds you share." + +[footerFreeSoftware] +other = "Free software (AGPL-3.0). No ads, no commissions, no data selling." + +[footerPublishedBy] +other = "Published by" + +[footerContact] +other = "Contact" + +[backToHome] +other = "Back to home" diff --git a/site/i18n/es.toml b/site/i18n/es.toml new file mode 100644 index 0000000..c8e795b --- /dev/null +++ b/site/i18n/es.toml @@ -0,0 +1,35 @@ +[skipToContent] +other = "Saltar al contenido" + +[navAbout] +other = "Qué es Tane" + +[navLegal] +other = "Legal" + +[navHome] +other = "Inicio" + +[screenshotsTitle] +other = "Un vistazo por dentro" + +[getItTitle] +other = "Consigue Tane" + +[getItComingSoon] +other = "Pronto en las tiendas de aplicaciones. Mientras tanto, Tane es software libre que puedes compilar y usar hoy." + +[legalIntro] +other = "Los documentos, en lenguaje claro, que rigen Tane. En resumen: tus datos se quedan en tu dispositivo, Tane es solo una herramienta, y tú eres responsable de las semillas que compartes." + +[footerFreeSoftware] +other = "Software libre (AGPL-3.0). Sin anuncios, sin comisiones, sin venta de datos." + +[footerPublishedBy] +other = "Publicado por" + +[footerContact] +other = "Contacto" + +[backToHome] +other = "Volver al inicio" diff --git a/site/layouts/404.html b/site/layouts/404.html new file mode 100644 index 0000000..872ebf1 --- /dev/null +++ b/site/layouts/404.html @@ -0,0 +1,7 @@ +{{ define "main" }} +<section class="doc"> + <h1>404</h1> + <p class="lead">This page could not be found.</p> + <p class="crumb"><a href="{{ "/" | relLangURL }}">← {{ T "backToHome" }}</a></p> +</section> +{{ end }} diff --git a/site/layouts/_default/baseof.html b/site/layouts/_default/baseof.html new file mode 100644 index 0000000..60e178e --- /dev/null +++ b/site/layouts/_default/baseof.html @@ -0,0 +1,14 @@ +<!doctype html> +<html lang="{{ .Site.Language.LanguageCode }}" dir="{{ or .Site.Language.LanguageDirection "ltr" }}"> + <head> + {{ partial "head.html" . }} + </head> + <body> + <a class="skip-link" href="#main">{{ T "skipToContent" }}</a> + {{ partial "header.html" . }} + <main id="main"> + {{ block "main" . }}{{ end }} + </main> + {{ partial "footer.html" . }} + </body> +</html> diff --git a/site/layouts/_default/list.html b/site/layouts/_default/list.html new file mode 100644 index 0000000..fc08158 --- /dev/null +++ b/site/layouts/_default/list.html @@ -0,0 +1,15 @@ +{{ define "main" }} +<section class="doc doc-list"> + <h1>{{ .Title }}</h1> + <p class="lead">{{ T "legalIntro" }}</p> + <ul class="legal-index"> + {{ range .Pages.ByWeight }} + <li> + <a href="{{ .RelPermalink }}">{{ .Title }}</a> + {{ with .Description }}<span class="desc">{{ . }}</span>{{ end }} + </li> + {{ end }} + </ul> + <p class="crumb"><a href="{{ "/" | relLangURL }}">← {{ T "backToHome" }}</a></p> +</section> +{{ end }} diff --git a/site/layouts/_default/single.html b/site/layouts/_default/single.html new file mode 100644 index 0000000..dd251ae --- /dev/null +++ b/site/layouts/_default/single.html @@ -0,0 +1,10 @@ +{{ define "main" }} +<article class="doc"> + {{ if eq .Section "legal" }}<p class="crumb"><a href="{{ "/legal/" | relLangURL }}">{{ T "navLegal" }}</a></p>{{ end }} + <h1>{{ .Title }}</h1> + <div class="doc-body"> + {{ .Content }} + </div> + <p class="crumb"><a href="{{ "/" | relLangURL }}">← {{ T "backToHome" }}</a></p> +</article> +{{ end }} diff --git a/site/layouts/index.html b/site/layouts/index.html new file mode 100644 index 0000000..28d77a1 --- /dev/null +++ b/site/layouts/index.html @@ -0,0 +1,53 @@ +{{ define "main" }} +{{ $lang := .Site.Language.Lang }} +<section class="hero"> + <div class="hero-copy"> + <h1>{{ .Params.hero.title }}</h1> + <p class="tagline">{{ .Params.hero.tagline }}</p> + <p class="lead">{{ .Params.hero.lead }}</p> + {{ .Content }} + {{ partial "store-badges.html" . }} + </div> + {{ $shot := resources.Get (printf "screenshots/%s/inventory.png" $lang) }} + {{ with $shot }} + <div class="hero-shot"> + <div class="phone"><img src="{{ .RelPermalink }}" alt="Tane inventory" loading="eager" /></div> + </div> + {{ end }} +</section> + +<section class="pillars"> + {{ range .Params.pillars }} + <article class="pillar"> + <div class="pillar-icon" aria-hidden="true">{{ .icon }}</div> + <h2>{{ .title }}</h2> + <p>{{ .body }}</p> + </article> + {{ end }} +</section> + +{{ $screens := slice "home" "inventory" "market" "calendar" "detail" }} +<section class="shots"> + <h2>{{ T "screenshotsTitle" }}</h2> + <div class="shot-row"> + {{ range $s := $screens }} + {{ $img := resources.Get (printf "screenshots/%s/%s.png" $lang $s) }} + {{ with $img }} + <div class="phone"><img src="{{ .RelPermalink }}" alt="Tane {{ $s }}" loading="lazy" /></div> + {{ end }} + {{ end }} + </div> +</section> + +<section class="values"> + <h2>{{ .Params.values.title }}</h2> + <ul> + {{ range .Params.values.points }}<li>{{ . }}</li>{{ end }} + </ul> +</section> + +<section class="get-it" id="get"> + <h2>{{ T "getItTitle" }}</h2> + {{ partial "store-badges.html" . }} +</section> +{{ end }} diff --git a/site/layouts/partials/footer.html b/site/layouts/partials/footer.html new file mode 100644 index 0000000..0eb06d9 --- /dev/null +++ b/site/layouts/partials/footer.html @@ -0,0 +1,16 @@ +<footer class="site-footer"> + <div class="foot-inner"> + <nav class="foot-legal"> + {{ range (where .Site.Pages "Section" "legal") }} + {{ if .IsPage }}<a href="{{ .RelPermalink }}">{{ .Title }}</a>{{ end }} + {{ end }} + </nav> + <p class="foot-note">{{ T "footerFreeSoftware" }}</p> + <p class="foot-note"> + {{ T "footerPublishedBy" }} + <a href="{{ .Site.Params.publisherURL }}">{{ .Site.Params.publisher }}</a> + · {{ T "footerContact" }}: + <a href="mailto:{{ .Site.Params.email }}">{{ .Site.Params.email }}</a> + </p> + </div> +</footer> diff --git a/site/layouts/partials/head.html b/site/layouts/partials/head.html new file mode 100644 index 0000000..40a6c58 --- /dev/null +++ b/site/layouts/partials/head.html @@ -0,0 +1,16 @@ +<meta charset="utf-8" /> +<meta name="viewport" content="width=device-width, initial-scale=1" /> +<title>{{ .Title }} · {{ .Site.Title }} + + + + +{{ range .AllTranslations }} + +{{ end }} + + + + +{{ $css := resources.Get "css/main.css" | minify | fingerprint }} + diff --git a/site/layouts/partials/header.html b/site/layouts/partials/header.html new file mode 100644 index 0000000..c9e64a6 --- /dev/null +++ b/site/layouts/partials/header.html @@ -0,0 +1,11 @@ + diff --git a/site/layouts/partials/lang-switch.html b/site/layouts/partials/lang-switch.html new file mode 100644 index 0000000..356d294 --- /dev/null +++ b/site/layouts/partials/lang-switch.html @@ -0,0 +1,16 @@ +{{- /* Links to this page in the other language; falls back to that language's home. */ -}} + + {{- range .Site.Languages -}} + {{- $lang := . -}} + {{- if eq $lang.Lang $.Site.Language.Lang -}} + {{ $lang.LanguageName }} + {{- else -}} + {{- $target := "" -}} + {{- range $.AllTranslations -}} + {{- if eq .Language.Lang $lang.Lang -}}{{- $target = .RelPermalink -}}{{- end -}} + {{- end -}} + {{- if not $target -}}{{- $target = ($lang.Lang | printf "/%s/" | relURL) -}}{{- end -}} + {{ $lang.LanguageName }} + {{- end -}} + {{- end -}} + diff --git a/site/layouts/partials/store-badges.html b/site/layouts/partials/store-badges.html new file mode 100644 index 0000000..ec6d5c7 --- /dev/null +++ b/site/layouts/partials/store-badges.html @@ -0,0 +1,12 @@ +{{- /* Renders store links only when configured, so nothing broken shows pre-launch. */ -}} +
+ {{- with .Site.Params.playStoreURL -}} + Google Play + {{- end -}} + {{- with .Site.Params.fdroidURL -}} + F-Droid + {{- end -}} + {{- if and (not .Site.Params.playStoreURL) (not .Site.Params.fdroidURL) -}} +

{{ T "getItComingSoon" }}

+ {{- end -}} +
diff --git a/site/nginx.conf b/site/nginx.conf new file mode 100644 index 0000000..a655aad --- /dev/null +++ b/site/nginx.conf @@ -0,0 +1,23 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Hugo emits directory-style URLs (/legal/privacy/index.html). + location / { + try_files $uri $uri/ =404; + } + + # Long-cache fingerprinted assets and images; the HTML stays fresh. + location ~* \.(png|jpg|jpeg|svg|webp|woff2?|css|js)$ { + expires 7d; + add_header Cache-Control "public"; + } + + error_page 404 /404.html; + + gzip on; + gzip_types text/css application/javascript image/svg+xml application/xml; + gzip_min_length 512; +} diff --git a/site/static/apple-touch-icon.png b/site/static/apple-touch-icon.png new file mode 100644 index 0000000..a2db482 Binary files /dev/null and b/site/static/apple-touch-icon.png differ diff --git a/site/static/favicon.png b/site/static/favicon.png new file mode 100644 index 0000000..91f8e59 Binary files /dev/null and b/site/static/favicon.png differ diff --git a/site/static/logo.png b/site/static/logo.png new file mode 100644 index 0000000..a2db482 Binary files /dev/null and b/site/static/logo.png differ diff --git a/site/static/robots.txt b/site/static/robots.txt new file mode 100644 index 0000000..f8c7410 --- /dev/null +++ b/site/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://tane.comunes.org/sitemap.xml