Changeset 5f483e3 in Klonkt


Ignore:
Timestamp:
06/27/2026 03:01:13 PM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
3746f6a
Parents:
98fe58a
Message:

chore: code cleanup — remove dead files, deps, DB tables, Google-login remnants

  • Deleted dead files: routes/notifications.js + services/NotificationService.js + views/pages/notifications.ejs (old bell, unmounted; live fedi notifications live in posts.js); deploy/verify.ps1 (stale smoke test).
  • package.json — drop unused ws dependency (no WebSocket since Prutter was removed).
  • .env.example — drop GOOGLE_* vars (Google login removed).
  • config/database.js — remove CREATE statements for dead tables (circle_links/remote_actors/ remote_posts from old Circles v1; user_notifications; post_likes). No DROP — existing rows stay.
  • services/i18n.js — remove dead keys: hub, prutter, and the whole Google-login set (+ the agoog.* wizard + glog.* notices, ~280 lines). Kept aseo.verify_google. solo_desc/login_google_only de-Googled.
  • views/pages/auth-login.ejs — drop the dead Google error block + button CSS; the password login form is unchanged.
  • deploy/post-receive — fix stale pm2 reload prutcms -> klonkt.
Files:
4 deleted
6 edited

Legend:

Unmodified
Added
Removed
  • .env.example

    r98fe58a r5f483e3  
    2727# `npm run reset-admin` on the server.
    2828
    29 # ── Google login for LISTENERS (optional) ───────────────────────────
    30 # Want listeners to sign in with Google to comment? Create your OWN Google OAuth
    31 # client (Google Cloud Console -> Credentials -> OAuth client ID -> "Web
    32 # application") and fill it in below. Your own client = your own Google Cloud
    33 # project; this site isn't centrally tied to anything. Not set? Then there's
    34 # simply no Google button. Google login NEVER grants admin rights (commenting only).
    35 # GOOGLE_REDIRECT_URI = <your domain>/auth/google/callback (set it exactly in the client)
    36 GOOGLE_CLIENT_ID=
    37 GOOGLE_CLIENT_SECRET=
    38 GOOGLE_REDIRECT_URI=https://example.com/auth/google/callback
    39 
    4029# ── Email (optional) — for "forgot password" reset links ────────────
    4130# Not set? Then reset only works via `npm run reset-admin` on the server.
  • deploy/post-receive

    r98fe58a r5f483e3  
    3535    fi
    3636
    37     # 4. pm2 reload — proces heet "prutcms" (legacy naam)
    38     pm2 reload prutcms 2>/dev/null || pm2 reload prutfolio 2>/dev/null || true
     37    # 4. pm2 reload — process is named "klonkt"
     38    pm2 reload klonkt 2>/dev/null || true
    3939
    4040    echo ">>> Done. App-files in $DEPLOY ongemoeid (.env, node_modules, storage)."
  • package.json

    r98fe58a r5f483e3  
    3232    "nodemailer": "^6.9.15",
    3333    "sanitize-html": "^2.17.3",
    34     "uuid": "^9.0.1",
    35     "ws": "^8.18.0"
     34    "uuid": "^9.0.1"
    3635  },
    3736  "engines": {
  • src/config/database.js

    r98fe58a r5f483e3  
    163163  `);
    164164
    165   // ── Circles (federation) ────────────────────────────────────
    166   // Decentralised, asymmetric connections between solo instances.
    167   db.exec(`
    168     CREATE TABLE IF NOT EXISTS circle_links (
    169       id TEXT PRIMARY KEY,
    170       local_site_id TEXT NOT NULL,
    171       remote_url TEXT NOT NULL,
    172       remote_actor_id TEXT,
    173       label TEXT,
    174       status TEXT DEFAULT 'active',
    175       added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    176       last_synced DATETIME,
    177       last_error TEXT,
    178       UNIQUE(local_site_id, remote_url),
    179       FOREIGN KEY (local_site_id) REFERENCES sites(id)
    180     );
    181     CREATE TABLE IF NOT EXISTS remote_actors (
    182       id TEXT PRIMARY KEY,
    183       url TEXT UNIQUE NOT NULL,
    184       name TEXT,
    185       summary TEXT,
    186       avatar TEXT,
    187       public_key TEXT NOT NULL,
    188       fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP
    189     );
    190     CREATE TABLE IF NOT EXISTS remote_posts (
    191       id TEXT PRIMARY KEY,
    192       actor_id TEXT NOT NULL,
    193       published DATETIME,
    194       title TEXT,
    195       summary TEXT,
    196       url TEXT,
    197       media_json TEXT,
    198       raw_json TEXT,
    199       fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    200       FOREIGN KEY (actor_id) REFERENCES remote_actors(id)
    201     );
    202   `);
    203 
    204   // Tags from the original post — shown in the circle feed (comma-separated string).
    205   ensureColumn('remote_posts', 'tags', 'TEXT');
    206 
    207165  // Newsletter / mailing list (premium). Subscribers per site; double opt-in when SMTP
    208166  // is configured (status 'pending' until confirmed), otherwise single opt-in ('confirmed').
     
    252210  `);
    253211
    254   // Notifications: someone replies to your comment / post, or likes your post. Snapshots
    255   // of name/title so the list can be shown cheaply without joins.
    256   // NB: deliberately named 'user_notifications' — some older DBs still have a stale,
    257   // unused 'notifications' table with a different schema (no read column).
    258   db.exec(`
    259     CREATE TABLE IF NOT EXISTS user_notifications (
    260       id TEXT PRIMARY KEY,
    261       user_id TEXT NOT NULL,
    262       type TEXT NOT NULL,
    263       actor_id TEXT,
    264       actor_name TEXT,
    265       post_slug TEXT,
    266       post_title TEXT,
    267       url TEXT,
    268       read INTEGER DEFAULT 0,
    269       created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    270     );
    271   `);
    272   // Older DBs may have a user_notifications table predating these columns — add
    273   // them before the index (which references `read`), else boot crashes.
    274   ensureColumn('user_notifications', 'type', 'TEXT');
    275   ensureColumn('user_notifications', 'actor_id', 'TEXT');
    276   ensureColumn('user_notifications', 'actor_name', 'TEXT');
    277   ensureColumn('user_notifications', 'post_slug', 'TEXT');
    278   ensureColumn('user_notifications', 'post_title', 'TEXT');
    279   ensureColumn('user_notifications', 'url', 'TEXT');
    280   ensureColumn('user_notifications', 'read', 'INTEGER DEFAULT 0');
    281   db.exec('CREATE INDEX IF NOT EXISTS idx_unotif_user ON user_notifications(user_id, read, created_at);');
    282 
    283212  // Link-in-bio click statistics (premium #6). One counter per (site, url); the
    284213  // link-in-bio page links via /links/go/:i which counts the click and redirects.
     
    293222  `);
    294223
    295   // Likes / favourites: a logged-in user can like a post. The set of
    296   // posts a user liked = their favourites (/favorieten page). One row
    297   // per (post, user); unique so that liking is idempotent.
    298   db.exec(`
    299     CREATE TABLE IF NOT EXISTS post_likes (
    300       post_id TEXT NOT NULL,
    301       user_id TEXT NOT NULL,
    302       created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    303       PRIMARY KEY (post_id, user_id)
    304     );
    305     CREATE INDEX IF NOT EXISTS idx_post_likes_user ON post_likes(user_id, created_at);
    306     CREATE INDEX IF NOT EXISTS idx_post_likes_post ON post_likes(post_id);
    307   `);
    308224
    309225  // ── ActivityPub (fediverse bridge) ──────────────────────────
  • src/services/i18n.js

    r98fe58a r5f483e3  
    6969    'admin.b_makesite': '🎨 Maak je site aan', 'admin.b_circle': '🔗 Cirkel', 'admin.b_stats': '📊 Statistieken',
    7070    'admin.b_newsletter': '✉️ Nieuwsbrief', 'admin.b_perskit': '📰 Perskit', 'admin.b_downloads': '⬇ Downloads',
    71     'admin.b_linkbio': '🔗 Link-in-bio', 'admin.b_agenda': '📅 Agenda', 'admin.b_google': '🔑 Google',
     71    'admin.b_linkbio': '🔗 Link-in-bio', 'admin.b_agenda': '📅 Agenda',
    7272    'admin.b_updates': '🔄 Updates', 'admin.b_help': '📖 Handleiding', 'admin.b_fediverse': 'Mijn reacties',
    7373    'admin.st_users': 'Gebruikers', 'admin.st_sites': 'Sites', 'admin.st_posts': 'Posts', 'admin.st_published': 'Gepubliceerd',
     
    8787    'auth.password': 'Wachtwoord',
    8888    'auth.forgot': 'Wachtwoord vergeten?',
    89     'auth.google_btn': 'Inloggen met Google',
    9089    'auth.public_sub': 'Log in om te reageren en je favorieten te bewaren.',
    9190    'auth.admin_box_q': 'Beheerder?',
    9291    'auth.admin_box_sub': 'Log in met gebruikersnaam & wachtwoord',
    93     'auth.google_unavail': 'Inloggen met Google is op deze site nog niet ingesteld.',
    9492    'auth.create_admin': 'Beheerder aanmaken',
    9593    'auth.reg_intro': 'Eerste keer opzetten — maak je beheerdersaccount aan. Dit kan maar één keer.',
     
    138136    'aset.solo': 'Solo',
    139137    'aset.solo_title': 'één site (de jouwe).',
    140     'aset.solo_desc': 'Geen gebruikers-directory, geen site-wissel. Bezoekers kunnen wel met Google inloggen om te reageren.',
    141     'aset.hub': 'Hub',
    142     'aset.hub_title': 'bedrijfssite met',
     138    'aset.solo_desc': 'Geen gebruikers-directory, geen site-wissel.',
    143139    'aset.premium_badge': 'premium',
    144     'aset.hub_desc': 'Jij (admin) wijst gebruikers een eigen Klonkt Hub toe. Gesloten cirkel: geen open registratie.',
    145     'aset.hub_locked_note': 'Vereist premium — koppel Patreon in deze pagina.',
    146140    'aset.circle': 'Cirkels',
    147141    'aset.circle_title': 'solo + federatie.',
    148142    'aset.circle_desc': 'Eén eigen site die de publieke posts van andere Klonkt-sites toont. Asymmetrisch: jij bepaalt wie in jouw cirkel zit.',
    149143    'aset.save': 'Opslaan',
    150     'aset.hub_home': 'Hub-hoofdpagina',
    151     'aset.hub_home_help_1': 'De openbare hub-pagina',
    152     'aset.hub_home_help_2': 'is generiek — van geen enkele gebruiker. Hier stel je de naam en intro in. Jouw eigen Klonkt Hub blijft los bestaan onder',
    153144    'aset.name': 'Naam',
    154145    'aset.name_ph': 'bijv. Studio Noord',
     
    181172    'aset.patreon_not_connected': 'Nog niet gekoppeld.',
    182173    'aset.patreon_connect': 'Koppel Patreon',
    183     'aset.google_login': 'Google-login (bezoekers)',
    184     'aset.google_login_help': 'Laat bezoekers met Google inloggen om te reageren — met je eigen Google Cloud-client.',
    185174    'aset.status_set': 'Status: ingesteld',
    186175    'aset.not_set_yet': 'Nog niet ingesteld.',
    187     'aset.google_login_setup': 'Google-login instellen',
    188176    'aset.newsletter': 'Nieuwsbrief',
    189177    'aset.newsletter_help_1': 'Toon een',
     
    254242    'asite.moderation_moderate': 'Modereren (eerst in de wachtrij)',
    255243    'asite.enable_audio': 'Audiospeler + embeds inschakelen',
    256     'asite.enable_prutter': 'Prutter (DM&rsquo;s tussen leden) inschakelen',
    257     'asite.enable_prutter_hint': '— Hub-functie, premium',
    258244    'asite.links': 'Social / streaming-links',
    259245    'asite.links_hint': 'Getoond als merk-iconen op de profielkop. Voeg er zoveel toe als je wilt.',
     
    322308    'aseo.verify_yandex': 'Yandex',
    323309    'aseo.save': 'SEO opslaan',
    324     'agoog.title': 'Google-login',
    325     'agoog.intro': 'Hiermee kunnen bezoekers met hun <strong>Google-account</strong> inloggen om te reageren. Je stelt dit één keer in met je eigen (gratis) Google-account — zo\'n 5 minuten, geen creditcard of betaalde dienst nodig.',
    326     'agoog.g1': 'Open de Google Cloud Console en log in. Maak bovenaan een <strong>nieuw project</strong> (Project selecteren → Nieuw project), geef het een naam en klik op <strong>Maken</strong>.',
    327     'agoog.g2': 'Ga in het menu links naar <strong>APIs en services → OAuth-toestemmingsscherm</strong>. Kies <strong>Extern</strong>, vul de app-naam + je e-mail in en sla op. Vraagt het systeem om testgebruikers? Voeg dan jezelf toe.',
    328     'agoog.g3': 'Ga naar <strong>APIs en services → Inloggegevens → Inloggegevens maken → OAuth-client-ID</strong> en kies als type <strong>Webapplicatie</strong>.',
    329     'agoog.g4': 'Plak bij <strong>Geautoriseerde omleidings-URI\'s</strong> exact dit adres en klik op <strong>Maken</strong>:',
    330     'agoog.g5': 'Google toont nu een <strong>Client-ID</strong> en <strong>Client-secret</strong>. Kopieer die naar de velden hieronder en klik op <strong>Opslaan</strong>. Klaar!',
    331     'agoog.copy': 'Kopieer',
    332     'agoog.copied': 'Gekopieerd ✓',
    333     'agoog.warn_short': 'Let op: moet <strong>exact</strong> kloppen — met <code>https://</code>, het juiste domein, en <strong>geen</strong> schuine streep aan het eind. Anders krijg je "Error 400: redirect_uri_mismatch".',
    334     'agoog.need_baseurl': 'Stel eerst <code>PUBLIC_BASE_URL</code> in (de publieke URL van je site) — daar wordt dit adres van afgeleid.',
    335     'agoog.no_api_note': '<strong>Tip:</strong> je hoeft géén API\'s aan te zetten (geen "People API" o.i.d.). Alleen het toestemmingsscherm + de OAuth-client zijn nodig.',
    336     'agoog.back': 'Instellingen',
    337     'agoog.card_title': 'Google-login (bezoekers)',
    338     'agoog.card_help_pre': 'Laat bezoekers met Google inloggen om te reageren — met je',
    339     'agoog.card_help_own': 'eigen',
    340     'agoog.card_help_mid': 'Google Cloud-client. Maak er een aan op',
    341     'agoog.card_help_post': '(OAuth client-ID, type "Web application").',
    342     'agoog.status_set': 'Status: ingesteld&nbsp;✓',
    343     'agoog.status_unset': 'Nog niet ingesteld.',
    344     'agoog.guide_summary': 'Hoe stel ik dit in? (stap voor stap)',
    345     'agoog.step1_pre': 'Ga naar de',
    346     'agoog.step1_post': 'en maak (of kies) een project.',
    347     'agoog.step2_usertype': 'User type',
    348     'agoog.step2_scopes_pre': 'Voeg de scopes',
    349     'agoog.step2_scopes_and': 'en',
    350     'agoog.step2_scopes_post': 'toe.',
    351     'agoog.step2_testmode': '(In testmodus: voeg jezelf toe als testgebruiker.)',
    352     'agoog.step3_type': 'type',
    353     'agoog.step4_pre': 'Bij',
    354     'agoog.step4_post': ': plak de redirect-URI hieronder (moet exact matchen).',
    355     'agoog.step5_pre': 'Kopieer de',
    356     'agoog.step5_post': 'die je krijgt naar de velden hieronder en klik Opslaan.',
    357     'agoog.note_label': 'Let op:',
    358     'agoog.note_pre': 'je hoeft',
    359     'agoog.note_noapi': 'geen API\'s aan te zetten',
    360     'agoog.note_mid': '— "Google Cloud APIs", People API e.d. zijn',
    361     'agoog.note_not': 'niet',
    362     'agoog.note_post': 'nodig. Inloggen werkt via OpenID Connect (alleen het toestemmingsscherm + de OAuth-client).',
    363     'agoog.redirect_label': 'Redirect-URI — kopieer deze en plak \'m in Google',
    364     'agoog.rstep1_pre': 'Ga naar',
    365     'agoog.rstep2': 'Open jouw OAuth client-ID (type "Web application").',
    366     'agoog.rstep3_pre': 'Onder',
    367     'agoog.rstep3_mid': 'klik',
    368     'agoog.rstep3_post': 'en plak de regel hierboven.',
    369     'agoog.rstep4_pre': 'Klik',
    370     'agoog.rstep4_post': 'en wacht ~1 minuut.',
    371     'agoog.warn_pre': 'Moet',
    372     'agoog.warn_exact': 'exact',
    373     'agoog.warn_mid1': 'kloppen:',
    374     'agoog.warn_nohttp': 'geen',
    375     'agoog.warn_mid2': ', juiste domein, en',
    376     'agoog.warn_noslash': 'geen',
    377     'agoog.warn_mid3': 'schuine streep aan het eind. Anders krijg je',
    378     'agoog.warn_error': '"Error 400: redirect_uri_mismatch"',
    379     'agoog.set_baseurl_pre': 'Stel eerst',
    380     'agoog.set_baseurl_post': 'in — daar wordt de redirect-URI van afgeleid.',
    381     'agoog.client_id': 'Client ID',
    382     'agoog.client_secret': 'Client Secret',
    383     'agoog.client_secret_set': 'ingesteld; leeg laten = ongewijzigd',
    384     'agoog.secret_ph_set': '•••••••• (ingesteld)',
    385     'agoog.save': 'Opslaan',
    386     'agoog.disconnect': 'Loskoppelen',
    387     'agoog.premium_locked': 'Premium-laag staat aan zonder Patreon — de Google-knop blijft verborgen tot premium ontgrendeld is.',
    388310    'aaud.title': 'Audio tracks',
    389311    'aaud.tagline_pre': 'MP3’s op site-niveau. Gebruik',
     
    696618    'ahelp.s_premium_h': 'Premium / Patreon',
    697619    'ahelp.s_premium_b': 'Premium-functies (Statistieken, Agenda, Downloads, Perskit, Nieuwsbrief, Link-in-bio, Embed) ontgrendel je in Beheer → Instellingen door Patreon te koppelen ($16 lifetime). Updates en de kernapp blijven altijd gratis.',
    698     'ahelp.s_google_h': 'Google-login (bezoekers)',
    699     'ahelp.s_google_b': 'Beheer → Instellingen → <strong>Google</strong>. Optioneel: laat bezoekers/fans met Google inloggen om te reageren. Jij beheert met je wachtwoord — Google geeft nooit beheerrechten. Vul je eigen Google-client-gegevens in (per site).',
    700620    'ahelp.s_updates_h': 'Updates',
    701621    'ahelp.s_updates_b': 'Beheer → <strong>Updates</strong> (alleen god) toont de huidige versie en of er een nieuwere is. Met "Nu bijwerken" haal je de laatste versie binnen.',
     
    836756    'acct.password_min': '(min 8 tekens)',
    837757    'acct.password_confirm': 'Bevestig nieuw wachtwoord',
    838     'acct.google_login': 'Inloggen met Google',
    839     'acct.google_linked': '✓ Je Google-account is gekoppeld — je kunt ook met Google inloggen.',
    840     'acct.google_unlink': 'Google ontkoppelen',
    841     'acct.google_link_hint': 'Koppel je Google-account zodat je voortaan ook met één klik via Google kunt inloggen (naast je wachtwoord).',
    842     'acct.google_link': 'Koppel Google-account',
    843758    'acct.login': 'Inloggen',
    844     'acct.login_google_only': 'Je bent ingelogd via Google ({email}). Er is geen apart wachtwoord.',
     759    'acct.login_google_only': 'Dit account heeft geen wachtwoord ({email}). Gebruik "Wachtwoord vergeten" om er een in te stellen.',
    845760    'news.this_artist': 'deze artiest',
    846761    'news.form_title': 'Blijf op de hoogte',
     
    938853    'adir.page_info': 'Pagina {page} van {pages}',
    939854    'adir.next': 'Volgende →',
    940     'glog.admin_title': 'Dit is een beheerders-account',
    941     'glog.admin_body': 'Je logde in met Google, maar dit e-mailadres hoort bij de beheerder van deze site. Inloggen met Google is alleen voor luisteraars/fans — beheerders gebruiken altijd hun wachtwoord (zo kan een Google-login nooit per ongeluk beheerrechten geven).',
    942     'glog.admin_li1': '<strong>Ben je de beheerder?</strong> <a href="/auth/admin">Log in met je gebruikersnaam + wachtwoord</a>.',
    943     'glog.admin_li2': '<strong>Wil je voortaan met Google inloggen?</strong> Log eerst in met wachtwoord en <strong>koppel je Google-account</strong> onder Account → daarna werkt Google-login ook voor jou.',
    944     'glog.admin_li3': '<strong>Wil je als fan inloggen?</strong> Gebruik een <strong>ander Google-account</strong> (niet je beheerdersadres).',
    945     'glog.session_title': 'Login afgebroken',
    946     'glog.session_body': 'De Google-login is afgebroken of de sessie was verlopen. Probeer het gewoon opnieuw.',
    947     'glog.email_title': 'Geen geverifieerd e-mailadres',
    948     'glog.email_body': 'Je Google-account heeft geen geverifieerd e-mailadres. Verifieer je adres bij Google.',
    949     'glog.linked_title': 'Adres al gekoppeld',
    950     'glog.linked_body': 'Dit e-mailadres is al aan een ander Google-account gekoppeld. Gebruik dat account, of neem contact op met de beheerder.',
    951     'glog.unavailable_title': 'Google-login niet beschikbaar',
    952     'glog.unavailable_body': 'Inloggen met Google is op deze site (nog) niet ingesteld.',
    953     'glog.failed_title': 'Google-login mislukt',
    954     'glog.failed_body': 'Er ging iets mis bij het inloggen met Google. Probeer het opnieuw.',
    955855    'pusr.post_one': 'post',
    956856    'pusr.post_many': 'posts',
     
    1088988    'admin.b_makesite': '🎨 Create your site', 'admin.b_circle': '🔗 Circle', 'admin.b_stats': '📊 Statistics',
    1089989    'admin.b_newsletter': '✉️ Newsletter', 'admin.b_perskit': '📰 Press kit', 'admin.b_downloads': '⬇ Downloads',
    1090     'admin.b_linkbio': '🔗 Link-in-bio', 'admin.b_agenda': '📅 Agenda', 'admin.b_google': '🔑 Google',
     990    'admin.b_linkbio': '🔗 Link-in-bio', 'admin.b_agenda': '📅 Agenda',
    1091991    'admin.b_updates': '🔄 Updates', 'admin.b_help': '📖 Manual', 'admin.b_fediverse': 'My replies',
    1092992    'admin.st_users': 'Users', 'admin.st_sites': 'Sites', 'admin.st_posts': 'Posts', 'admin.st_published': 'Published',
     
    11041004    'auth.password': 'Password',
    11051005    'auth.forgot': 'Forgot password?',
    1106     'auth.google_btn': 'Sign in with Google',
    11071006    'auth.public_sub': 'Log in to comment and save your favorites.',
    11081007    'auth.admin_box_q': 'Admin?',
    11091008    'auth.admin_box_sub': 'Log in with username & password',
    1110     'auth.google_unavail': 'Google sign-in isn’t set up on this site yet.',
    11111009    'auth.create_admin': 'Create admin',
    11121010    'auth.reg_intro': 'First-time setup — create your admin account. This can only be done once.',
     
    11521050    'aset.solo': 'Solo',
    11531051    'aset.solo_title': 'one site (yours).',
    1154     'aset.solo_desc': 'No user directory, no site switching. Visitors can still sign in with Google to comment.',
    1155     'aset.hub': 'Hub',
    1156     'aset.hub_title': 'company site with',
     1052    'aset.solo_desc': 'No user directory, no site switching.',
    11571053    'aset.premium_badge': 'premium',
    1158     'aset.hub_desc': 'You (the admin) assign users their own Klonkt Hub. Closed circle: no open registration.',
    1159     'aset.hub_locked_note': 'Requires premium — connect Patreon on this page.',
    11601054    'aset.circle': 'Circles',
    11611055    'aset.circle_title': 'solo + federation.',
    11621056    'aset.circle_desc': 'A single site of your own that shows the public posts of other Klonkt sites. Asymmetric: you decide who is in your circle.',
    11631057    'aset.save': 'Save',
    1164     'aset.hub_home': 'Hub home page',
    1165     'aset.hub_home_help_1': 'The public hub page',
    1166     'aset.hub_home_help_2': 'is generic — it belongs to no single user. Here you set the name and intro. Your own Klonkt Hub stays separate under',
    11671058    'aset.name': 'Name',
    11681059    'aset.name_ph': 'e.g. Studio Noord',
     
    11951086    'aset.patreon_not_connected': 'Not connected yet.',
    11961087    'aset.patreon_connect': 'Connect Patreon',
    1197     'aset.google_login': 'Google login (visitors)',
    1198     'aset.google_login_help': 'Let visitors sign in with Google to comment — using your own Google Cloud client.',
    11991088    'aset.status_set': 'Status: configured',
    12001089    'aset.not_set_yet': 'Not configured yet.',
    1201     'aset.google_login_setup': 'Set up Google login',
    12021090    'aset.newsletter': 'Newsletter',
    12031091    'aset.newsletter_help_1': 'Show a',
     
    12681156    'asite.moderation_moderate': 'Moderate (queue first)',
    12691157    'asite.enable_audio': 'Enable audio player + embeds',
    1270     'asite.enable_prutter': 'Enable Prutter (DMs between members)',
    1271     'asite.enable_prutter_hint': '— Hub feature, premium',
    12721158    'asite.links': 'Social / streaming links',
    12731159    'asite.links_hint': 'Shown as brand icons in the profile header. Add as many as you like.',
     
    13361222    'aseo.verify_yandex': 'Yandex',
    13371223    'aseo.save': 'Save SEO',
    1338     'agoog.title': 'Google login',
    1339     'agoog.intro': 'This lets visitors sign in with their <strong>Google account</strong> to comment. You set it up once with your own (free) Google account — about 5 minutes, no credit card or paid service needed.',
    1340     'agoog.g1': 'Open the Google Cloud Console and sign in. At the top, create a <strong>new project</strong> (Select project → New project), give it a name and click <strong>Create</strong>.',
    1341     'agoog.g2': 'In the left menu go to <strong>APIs & Services → OAuth consent screen</strong>. Choose <strong>External</strong>, fill in the app name + your email and save. If it asks for test users, add yourself.',
    1342     'agoog.g3': 'Go to <strong>APIs & Services → Credentials → Create credentials → OAuth client ID</strong> and choose type <strong>Web application</strong>.',
    1343     'agoog.g4': 'Under <strong>Authorized redirect URIs</strong> paste exactly this address and click <strong>Create</strong>:',
    1344     'agoog.g5': 'Google now shows a <strong>Client ID</strong> and <strong>Client secret</strong>. Copy them into the fields below and click <strong>Save</strong>. Done!',
    1345     'agoog.copy': 'Copy',
    1346     'agoog.copied': 'Copied ✓',
    1347     'agoog.warn_short': 'Note: it must match <strong>exactly</strong> — with <code>https://</code>, the correct domain, and <strong>no</strong> trailing slash. Otherwise you get "Error 400: redirect_uri_mismatch".',
    1348     'agoog.need_baseurl': 'First set <code>PUBLIC_BASE_URL</code> (the public URL of your site) — this address is derived from it.',
    1349     'agoog.no_api_note': '<strong>Tip:</strong> you do not need to enable any APIs (no "People API" etc.). Only the consent screen + the OAuth client are needed.',
    1350     'agoog.back': 'Settings',
    1351     'agoog.card_title': 'Google login (visitors)',
    1352     'agoog.card_help_pre': 'Let visitors log in with Google to comment — using your',
    1353     'agoog.card_help_own': 'own',
    1354     'agoog.card_help_mid': 'Google Cloud client. Create one at',
    1355     'agoog.card_help_post': '(OAuth client ID, type "Web application").',
    1356     'agoog.status_set': 'Status: configured&nbsp;✓',
    1357     'agoog.status_unset': 'Not configured yet.',
    1358     'agoog.guide_summary': 'How do I set this up? (step by step)',
    1359     'agoog.step1_pre': 'Go to the',
    1360     'agoog.step1_post': 'and create (or pick) a project.',
    1361     'agoog.step2_usertype': 'User type',
    1362     'agoog.step2_scopes_pre': 'Add the scopes',
    1363     'agoog.step2_scopes_and': 'and',
    1364     'agoog.step2_scopes_post': '.',
    1365     'agoog.step2_testmode': '(In testing mode: add yourself as a test user.)',
    1366     'agoog.step3_type': 'type',
    1367     'agoog.step4_pre': 'Under',
    1368     'agoog.step4_post': ': paste the redirect URI below (it must match exactly).',
    1369     'agoog.step5_pre': 'Copy the',
    1370     'agoog.step5_post': 'you receive into the fields below and click Save.',
    1371     'agoog.note_label': 'Note:',
    1372     'agoog.note_pre': 'you do',
    1373     'agoog.note_noapi': 'not need to enable any APIs',
    1374     'agoog.note_mid': '— "Google Cloud APIs", People API and the like are',
    1375     'agoog.note_not': 'not',
    1376     'agoog.note_post': 'required. Login works via OpenID Connect (just the consent screen + the OAuth client).',
    1377     'agoog.redirect_label': 'Redirect URI — copy this and paste it into Google',
    1378     'agoog.rstep1_pre': 'Go to',
    1379     'agoog.rstep2': 'Open your OAuth client ID (type "Web application").',
    1380     'agoog.rstep3_pre': 'Under',
    1381     'agoog.rstep3_mid': 'click',
    1382     'agoog.rstep3_post': 'and paste the line above.',
    1383     'agoog.rstep4_pre': 'Click',
    1384     'agoog.rstep4_post': 'and wait ~1 minute.',
    1385     'agoog.warn_pre': 'It must be',
    1386     'agoog.warn_exact': 'exactly',
    1387     'agoog.warn_mid1': 'right:',
    1388     'agoog.warn_nohttp': 'not',
    1389     'agoog.warn_mid2': ', the correct domain, and',
    1390     'agoog.warn_noslash': 'no',
    1391     'agoog.warn_mid3': 'trailing slash. Otherwise you get',
    1392     'agoog.warn_error': '"Error 400: redirect_uri_mismatch"',
    1393     'agoog.set_baseurl_pre': 'First set',
    1394     'agoog.set_baseurl_post': '— the redirect URI is derived from it.',
    1395     'agoog.client_id': 'Client ID',
    1396     'agoog.client_secret': 'Client Secret',
    1397     'agoog.client_secret_set': 'configured; leave blank to keep unchanged',
    1398     'agoog.secret_ph_set': '•••••••• (configured)',
    1399     'agoog.save': 'Save',
    1400     'agoog.disconnect': 'Disconnect',
    1401     'agoog.premium_locked': 'The premium tier is on without Patreon — the Google button stays hidden until premium is unlocked.',
    14021224    'aaud.title': 'Audio tracks',
    14031225    'aaud.tagline_pre': 'Site-level MP3s. Use',
     
    17101532    'ahelp.s_premium_h': 'Premium / Patreon',
    17111533    'ahelp.s_premium_b': 'You unlock premium features (Statistics, Agenda, Downloads, Press kit, Newsletter, Link-in-bio, Embed) in Admin → Settings by connecting Patreon ($16 lifetime). Updates and the core app always stay free.',
    1712     'ahelp.s_google_h': 'Google login (visitors)',
    1713     'ahelp.s_google_b': 'Admin → Settings → <strong>Google</strong>. Optional: let visitors/fans log in with Google to comment. You manage with your password — Google never grants admin rights. Enter your own Google client details (per site).',
    17141534    'ahelp.s_updates_h': 'Updates',
    17151535    'ahelp.s_updates_b': 'Admin → <strong>Updates</strong> (god only) shows the current version and whether a newer one is available. With "Update now" you fetch the latest version.',
     
    18491669    'acct.password_min': '(min 8 characters)',
    18501670    'acct.password_confirm': 'Confirm new password',
    1851     'acct.google_login': 'Sign in with Google',
    1852     'acct.google_linked': '✓ Your Google account is linked — you can also sign in with Google.',
    1853     'acct.google_unlink': 'Unlink Google',
    1854     'acct.google_link_hint': 'Link your Google account so you can also sign in with one click via Google (alongside your password).',
    1855     'acct.google_link': 'Link Google account',
    18561671    'acct.login': 'Sign in',
    1857     'acct.login_google_only': 'You are signed in via Google ({email}). There is no separate password.',
     1672    'acct.login_google_only': 'This account has no password ({email}). Use "Forgot password" to set one.',
    18581673    'news.this_artist': 'this artist',
    18591674    'news.form_title': 'Stay in the loop',
     
    19511766    'adir.page_info': 'Page {page} of {pages}',
    19521767    'adir.next': 'Next →',
    1953     'glog.admin_title': 'This is an administrator account',
    1954     'glog.admin_body': 'You signed in with Google, but this email address belongs to the administrator of this site. Google sign-in is only for listeners/fans — administrators always use their password (so a Google login can never accidentally grant admin rights).',
    1955     'glog.admin_li1': '<strong>Are you the administrator?</strong> <a href="/auth/admin">Log in with your username + password</a>.',
    1956     'glog.admin_li2': '<strong>Want to sign in with Google from now on?</strong> Log in with your password first and <strong>link your Google account</strong> under Account → after that Google sign-in works for you too.',
    1957     'glog.admin_li3': '<strong>Want to sign in as a fan?</strong> Use a <strong>different Google account</strong> (not your administrator address).',
    1958     'glog.session_title': 'Login interrupted',
    1959     'glog.session_body': 'The Google login was interrupted or the session had expired. Just try again.',
    1960     'glog.email_title': 'No verified email address',
    1961     'glog.email_body': 'Your Google account has no verified email address. Verify your address with Google.',
    1962     'glog.linked_title': 'Address already linked',
    1963     'glog.linked_body': 'This email address is already linked to another Google account. Use that account, or contact the administrator.',
    1964     'glog.unavailable_title': 'Google sign-in unavailable',
    1965     'glog.unavailable_body': 'Google sign-in has not (yet) been set up on this site.',
    1966     'glog.failed_title': 'Google sign-in failed',
    1967     'glog.failed_body': 'Something went wrong signing in with Google. Please try again.',
    19681768    'pusr.post_one': 'post',
    19691769    'pusr.post_many': 'posts',
     
    21011901    'admin.b_makesite': '🎨 Seite erstellen', 'admin.b_circle': '🔗 Kreis', 'admin.b_stats': '📊 Statistik',
    21021902    'admin.b_newsletter': '✉️ Newsletter', 'admin.b_perskit': '📰 Pressekit', 'admin.b_downloads': '⬇ Downloads',
    2103     'admin.b_linkbio': '🔗 Link-in-Bio', 'admin.b_agenda': '📅 Termine', 'admin.b_google': '🔑 Google',
     1903    'admin.b_linkbio': '🔗 Link-in-Bio', 'admin.b_agenda': '📅 Termine',
    21041904    'admin.b_updates': '🔄 Updates', 'admin.b_help': '📖 Anleitung', 'admin.b_fediverse': 'Meine Antworten',
    21051905    'admin.st_users': 'Nutzer', 'admin.st_sites': 'Seiten', 'admin.st_posts': 'Beiträge', 'admin.st_published': 'Veröffentlicht',
     
    21171917    'auth.password': 'Passwort',
    21181918    'auth.forgot': 'Passwort vergessen?',
    2119     'auth.google_btn': 'Mit Google anmelden',
    21201919    'auth.public_sub': 'Melde dich an, um zu kommentieren und Favoriten zu speichern.',
    21211920    'auth.admin_box_q': 'Administrator?',
    21221921    'auth.admin_box_sub': 'Mit Benutzername & Passwort anmelden',
    2123     'auth.google_unavail': 'Google-Anmeldung ist auf dieser Seite noch nicht eingerichtet.',
    21241922    'auth.create_admin': 'Administrator anlegen',
    21251923    'auth.reg_intro': 'Ersteinrichtung — lege dein Administrator-Konto an. Das geht nur einmal.',
     
    21651963    'aset.solo': 'Solo',
    21661964    'aset.solo_title': 'eine Site (deine).',
    2167     'aset.solo_desc': 'Kein Benutzerverzeichnis, kein Site-Wechsel. Besucher können sich aber mit Google anmelden, um zu kommentieren.',
    2168     'aset.hub': 'Hub',
    2169     'aset.hub_title': 'Unternehmens-Site mit',
     1965    'aset.solo_desc': 'Kein Benutzerverzeichnis, kein Site-Wechsel.',
    21701966    'aset.premium_badge': 'Premium',
    2171     'aset.hub_desc': 'Du (Admin) weist Benutzern einen eigenen Klonkt Hub zu. Geschlossener Kreis: keine offene Registrierung.',
    2172     'aset.hub_locked_note': 'Erfordert Premium — verbinde Patreon auf dieser Seite.',
    21731967    'aset.circle': 'Kreise',
    21741968    'aset.circle_title': 'Solo + Föderation.',
    21751969    'aset.circle_desc': 'Eine eigene Site, die die öffentlichen Beiträge anderer Klonkt-Sites anzeigt. Asymmetrisch: Du bestimmst, wer in deinem Kreis ist.',
    21761970    'aset.save': 'Speichern',
    2177     'aset.hub_home': 'Hub-Startseite',
    2178     'aset.hub_home_help_1': 'Die öffentliche Hub-Seite',
    2179     'aset.hub_home_help_2': 'ist generisch — sie gehört keinem einzelnen Benutzer. Hier legst du Name und Intro fest. Dein eigener Klonkt Hub bleibt separat unter',
    21801971    'aset.name': 'Name',
    21811972    'aset.name_ph': 'z. B. Studio Noord',
     
    22081999    'aset.patreon_not_connected': 'Noch nicht verbunden.',
    22092000    'aset.patreon_connect': 'Patreon verbinden',
    2210     'aset.google_login': 'Google-Login (Besucher)',
    2211     'aset.google_login_help': 'Lass Besucher sich mit Google anmelden, um zu kommentieren — mit deinem eigenen Google-Cloud-Client.',
    22122001    'aset.status_set': 'Status: eingerichtet',
    22132002    'aset.not_set_yet': 'Noch nicht eingerichtet.',
    2214     'aset.google_login_setup': 'Google-Login einrichten',
    22152003    'aset.newsletter': 'Newsletter',
    22162004    'aset.newsletter_help_1': 'Zeige ein',
     
    22812069    'asite.moderation_moderate': 'Moderieren (zuerst in die Warteschlange)',
    22822070    'asite.enable_audio': 'Audioplayer + Embeds aktivieren',
    2283     'asite.enable_prutter': 'Prutter (DMs zwischen Mitgliedern) aktivieren',
    2284     'asite.enable_prutter_hint': '— Hub-Funktion, Premium',
    22852071    'asite.links': 'Social- / Streaming-Links',
    22862072    'asite.links_hint': 'Werden als Marken-Icons im Profilkopf gezeigt. Füge so viele hinzu, wie du möchtest.',
     
    23492135    'aseo.verify_yandex': 'Yandex',
    23502136    'aseo.save': 'SEO speichern',
    2351     'agoog.title': 'Google-Anmeldung',
    2352     'agoog.intro': 'Damit können sich Besucher mit ihrem <strong>Google-Konto</strong> anmelden, um zu kommentieren. Du richtest das einmalig mit deinem eigenen (kostenlosen) Google-Konto ein — etwa 5 Minuten, keine Kreditkarte oder kostenpflichtiger Dienst nötig.',
    2353     'agoog.g1': 'Öffne die Google Cloud Console und melde dich an. Erstelle oben ein <strong>neues Projekt</strong> (Projekt auswählen → Neues Projekt), gib ihm einen Namen und klicke auf <strong>Erstellen</strong>.',
    2354     'agoog.g2': 'Gehe im linken Menü zu <strong>APIs und Dienste → OAuth-Zustimmungsbildschirm</strong>. Wähle <strong>Extern</strong>, trage den App-Namen + deine E-Mail ein und speichere. Falls nach Testnutzern gefragt wird, füge dich selbst hinzu.',
    2355     'agoog.g3': 'Gehe zu <strong>APIs und Dienste → Anmeldedaten → Anmeldedaten erstellen → OAuth-Client-ID</strong> und wähle den Typ <strong>Webanwendung</strong>.',
    2356     'agoog.g4': 'Füge unter <strong>Autorisierte Weiterleitungs-URIs</strong> genau diese Adresse ein und klicke auf <strong>Erstellen</strong>:',
    2357     'agoog.g5': 'Google zeigt nun eine <strong>Client-ID</strong> und ein <strong>Client-Secret</strong>. Kopiere sie in die Felder unten und klicke auf <strong>Speichern</strong>. Fertig!',
    2358     'agoog.copy': 'Kopieren',
    2359     'agoog.copied': 'Kopiert ✓',
    2360     'agoog.warn_short': 'Achtung: muss <strong>exakt</strong> stimmen — mit <code>https://</code>, der richtigen Domain und <strong>ohne</strong> Schrägstrich am Ende. Andernfalls bekommst du „Error 400: redirect_uri_mismatch“.',
    2361     'agoog.need_baseurl': 'Lege zuerst <code>PUBLIC_BASE_URL</code> fest (die öffentliche URL deiner Seite) — daraus wird diese Adresse abgeleitet.',
    2362     'agoog.no_api_note': '<strong>Tipp:</strong> du musst keine APIs aktivieren (keine „People API“ usw.). Nur der Zustimmungsbildschirm + der OAuth-Client sind nötig.',
    2363     'agoog.back': 'Einstellungen',
    2364     'agoog.card_title': 'Google-Anmeldung (Besucher)',
    2365     'agoog.card_help_pre': 'Lass Besucher sich mit Google anmelden, um zu kommentieren — mit deinem',
    2366     'agoog.card_help_own': 'eigenen',
    2367     'agoog.card_help_mid': 'Google-Cloud-Client. Erstelle einen unter',
    2368     'agoog.card_help_post': '(OAuth-Client-ID, Typ „Web application“).',
    2369     'agoog.status_set': 'Status: eingerichtet&nbsp;✓',
    2370     'agoog.status_unset': 'Noch nicht eingerichtet.',
    2371     'agoog.guide_summary': 'Wie richte ich das ein? (Schritt für Schritt)',
    2372     'agoog.step1_pre': 'Gehe zur',
    2373     'agoog.step1_post': 'und erstelle (oder wähle) ein Projekt.',
    2374     'agoog.step2_usertype': 'User type',
    2375     'agoog.step2_scopes_pre': 'Füge die Scopes',
    2376     'agoog.step2_scopes_and': 'und',
    2377     'agoog.step2_scopes_post': 'hinzu.',
    2378     'agoog.step2_testmode': '(Im Testmodus: füge dich selbst als Testnutzer hinzu.)',
    2379     'agoog.step3_type': 'Typ',
    2380     'agoog.step4_pre': 'Unter',
    2381     'agoog.step4_post': ': füge die Redirect-URI unten ein (muss exakt übereinstimmen).',
    2382     'agoog.step5_pre': 'Kopiere die',
    2383     'agoog.step5_post': 'die du erhältst, in die Felder unten und klicke auf Speichern.',
    2384     'agoog.note_label': 'Achtung:',
    2385     'agoog.note_pre': 'du musst',
    2386     'agoog.note_noapi': 'keine APIs aktivieren',
    2387     'agoog.note_mid': '— „Google Cloud APIs“, People API usw. sind',
    2388     'agoog.note_not': 'nicht',
    2389     'agoog.note_post': 'erforderlich. Die Anmeldung läuft über OpenID Connect (nur der Zustimmungsbildschirm + der OAuth-Client).',
    2390     'agoog.redirect_label': 'Redirect-URI — kopiere diese und füge sie in Google ein',
    2391     'agoog.rstep1_pre': 'Gehe zur',
    2392     'agoog.rstep2': 'Öffne deine OAuth-Client-ID (Typ „Web application“).',
    2393     'agoog.rstep3_pre': 'Unter',
    2394     'agoog.rstep3_mid': 'klicke auf',
    2395     'agoog.rstep3_post': 'und füge die obige Zeile ein.',
    2396     'agoog.rstep4_pre': 'Klicke auf',
    2397     'agoog.rstep4_post': 'und warte ~1 Minute.',
    2398     'agoog.warn_pre': 'Muss',
    2399     'agoog.warn_exact': 'exakt',
    2400     'agoog.warn_mid1': 'stimmen:',
    2401     'agoog.warn_nohttp': 'kein',
    2402     'agoog.warn_mid2': ', richtige Domain und',
    2403     'agoog.warn_noslash': 'kein',
    2404     'agoog.warn_mid3': 'Schrägstrich am Ende. Andernfalls bekommst du',
    2405     'agoog.warn_error': '„Error 400: redirect_uri_mismatch“',
    2406     'agoog.set_baseurl_pre': 'Lege zuerst',
    2407     'agoog.set_baseurl_post': 'fest — daraus wird die Redirect-URI abgeleitet.',
    2408     'agoog.client_id': 'Client ID',
    2409     'agoog.client_secret': 'Client Secret',
    2410     'agoog.client_secret_set': 'eingerichtet; leer lassen = unverändert',
    2411     'agoog.secret_ph_set': '•••••••• (eingerichtet)',
    2412     'agoog.save': 'Speichern',
    2413     'agoog.disconnect': 'Trennen',
    2414     'agoog.premium_locked': 'Die Premium-Ebene ist ohne Patreon aktiv — der Google-Button bleibt verborgen, bis Premium freigeschaltet ist.',
    24152137    'aaud.title': 'Audio-Tracks',
    24162138    'aaud.tagline_pre': 'MP3s auf Site-Ebene. Verwende',
     
    27232445    'ahelp.s_premium_h': 'Premium / Patreon',
    27242446    'ahelp.s_premium_b': 'Premium-Funktionen (Statistiken, Termine, Downloads, Pressekit, Newsletter, Link-in-Bio, Embed) schaltest du in Verwaltung → Einstellungen frei, indem du Patreon verknüpfst ($16 lebenslang). Updates und die Kern-App bleiben immer kostenlos.',
    2725     'ahelp.s_google_h': 'Google-Login (Besucher)',
    2726     'ahelp.s_google_b': 'Verwaltung → Einstellungen → <strong>Google</strong>. Optional: Lass Besucher/Fans sich mit Google anmelden, um zu kommentieren. Du verwaltest mit deinem Passwort — Google gewährt nie Verwaltungsrechte. Trage deine eigenen Google-Client-Daten ein (pro Seite).',
    27272447    'ahelp.s_updates_h': 'Updates',
    27282448    'ahelp.s_updates_b': 'Verwaltung → <strong>Updates</strong> (nur God) zeigt die aktuelle Version und ob eine neuere verfügbar ist. Mit „Jetzt aktualisieren“ holst du dir die neueste Version.',
     
    28622582    'acct.password_min': '(mind. 8 Zeichen)',
    28632583    'acct.password_confirm': 'Neues Passwort bestätigen',
    2864     'acct.google_login': 'Mit Google anmelden',
    2865     'acct.google_linked': '✓ Dein Google-Konto ist verknüpft — du kannst dich auch mit Google anmelden.',
    2866     'acct.google_unlink': 'Google trennen',
    2867     'acct.google_link_hint': 'Verknüpfe dein Google-Konto, damit du dich künftig auch mit einem Klick über Google anmelden kannst (zusätzlich zu deinem Passwort).',
    2868     'acct.google_link': 'Google-Konto verknüpfen',
    28692584    'acct.login': 'Anmelden',
    2870     'acct.login_google_only': 'Du bist über Google angemeldet ({email}). Es gibt kein separates Passwort.',
     2585    'acct.login_google_only': 'Dieses Konto hat kein Passwort ({email}). Nutze "Passwort vergessen", um eines festzulegen.',
    28712586    'news.this_artist': 'diese:r Künstler:in',
    28722587    'news.form_title': 'Bleib auf dem Laufenden',
     
    29642679    'adir.page_info': 'Seite {page} von {pages}',
    29652680    'adir.next': 'Weiter →',
    2966     'glog.admin_title': 'Dies ist ein Administrator-Konto',
    2967     'glog.admin_body': 'Du hast dich mit Google angemeldet, aber diese E-Mail-Adresse gehört dem Administrator dieser Seite. Die Google-Anmeldung ist nur für Hörer/Fans — Administratoren verwenden immer ihr Passwort (so kann eine Google-Anmeldung niemals versehentlich Administratorrechte erteilen).',
    2968     'glog.admin_li1': '<strong>Bist du der Administrator?</strong> <a href="/auth/admin">Melde dich mit Benutzername + Passwort an</a>.',
    2969     'glog.admin_li2': '<strong>Möchtest du dich künftig mit Google anmelden?</strong> Melde dich zuerst mit Passwort an und <strong>verknüpfe dein Google-Konto</strong> unter Konto → danach funktioniert die Google-Anmeldung auch für dich.',
    2970     'glog.admin_li3': '<strong>Möchtest du dich als Fan anmelden?</strong> Verwende ein <strong>anderes Google-Konto</strong> (nicht deine Administrator-Adresse).',
    2971     'glog.session_title': 'Anmeldung abgebrochen',
    2972     'glog.session_body': 'Die Google-Anmeldung wurde abgebrochen oder die Sitzung war abgelaufen. Versuche es einfach erneut.',
    2973     'glog.email_title': 'Keine verifizierte E-Mail-Adresse',
    2974     'glog.email_body': 'Dein Google-Konto hat keine verifizierte E-Mail-Adresse. Verifiziere deine Adresse bei Google.',
    2975     'glog.linked_title': 'Adresse bereits verknüpft',
    2976     'glog.linked_body': 'Diese E-Mail-Adresse ist bereits mit einem anderen Google-Konto verknüpft. Verwende dieses Konto oder wende dich an den Administrator.',
    2977     'glog.unavailable_title': 'Google-Anmeldung nicht verfügbar',
    2978     'glog.unavailable_body': 'Die Google-Anmeldung wurde auf dieser Seite (noch) nicht eingerichtet.',
    2979     'glog.failed_title': 'Google-Anmeldung fehlgeschlagen',
    2980     'glog.failed_body': 'Beim Anmelden mit Google ist etwas schiefgelaufen. Bitte versuche es erneut.',
    29812681    'pusr.post_one': 'Beitrag',
    29822682    'pusr.post_many': 'Beiträge',
  • src/views/pages/auth-login.ejs

    r98fe58a r5f483e3  
    11<%
    2   // adminLogin = the hidden admin login page (/auth/admin): shows the
    3   // username/password form. The public /auth/login shows visitors
    4   // ONLY Google login (listeners/fans). This keeps the admin login
    5   // off the page where visitors are directed.
     2  // The login page shows the admin username/password form. (Public listener
     3  // login via Google was removed — interaction now runs via the fediverse.)
    64  var _admin = (typeof adminLogin !== 'undefined' && adminLogin);
    75%>
     
    97  <h1><%= _admin ? t('auth.admin_login_title') : t('nav.login') %></h1>
    108  <% if (typeof success !== 'undefined' && success) { %><div class="alert alert-success"><%= success %></div><% } %>
    11   <%
    12     var GMAP = {
    13       admin: { icon: '🔐', title: t('glog.admin_title'),
    14         body: t('glog.admin_body'),
    15         list: [t('glog.admin_li1'), t('glog.admin_li2'), t('glog.admin_li3')] },
    16       session: { icon: '⏱️', title: t('glog.session_title'), body: t('glog.session_body') },
    17       email: { icon: '✉️', title: t('glog.email_title'), body: t('glog.email_body') },
    18       linked: { icon: '🔗', title: t('glog.linked_title'), body: t('glog.linked_body') },
    19       unavailable: { icon: '🚫', title: t('glog.unavailable_title'), body: t('glog.unavailable_body') },
    20       failed: { icon: '⚠️', title: t('glog.failed_title'), body: t('glog.failed_body') },
    21     };
    22     var _g = (typeof gerr !== 'undefined' && gerr && GMAP[gerr]) ? GMAP[gerr] : null;
    23   %>
    24   <% if (_g) { %>
    25     <div class="auth-notice">
    26       <div class="auth-notice-icon"><%= _g.icon %></div>
    27       <h2 class="auth-notice-title"><%= _g.title %></h2>
    28       <p><%= _g.body %></p>
    29       <% if (_g.list) { %><ul class="auth-notice-list"><% _g.list.forEach(function(li){ %><li><%- li %></li><% }); %></ul><% } %>
    30     </div>
    31   <% } else if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
     9  <% if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
    3210
    3311  <% if (_admin) { %>
     
    5129</div>
    5230
    53 <style>
    54   .auth-notice { border: 1px solid rgba(128,128,128,.28); border-radius: 14px; padding: 18px 20px; margin-bottom: 18px; background: rgba(128,128,128,.06); }
    55   .auth-notice-icon { font-size: 26px; }
    56   .auth-notice-title { font-size: 17px; margin: 4px 0 8px; }
    57   .auth-notice p { margin: 0 0 10px; line-height: 1.55; opacity: .9; }
    58   .auth-notice-list { margin: 0; padding-left: 18px; line-height: 1.6; }
    59   .auth-notice-list li { margin: 4px 0; }
    60   .auth-sub { color: var(--ink-muted); margin: 0 0 1rem; text-align: center; }
    61   /* Google button is white → keep text dark at all times, including hover (the global
    62      a.btn:hover rule would otherwise make it near-white on white = unreadable). */
    63   a.btn-google, a.btn-google:hover { color: #1f1f1f; }
    64   .auth-admin-box {
    65     display: flex; align-items: center; gap: .75rem;
    66     margin-top: 1.25rem; padding: .8rem 1rem;
    67     border: 1px solid var(--rule); border-radius: 12px;
    68     background: var(--paper-2); color: var(--ink); text-decoration: none;
    69     transition: border-color 120ms, background 120ms;
    70   }
    71   .auth-admin-box:hover { border-color: var(--accent); background: var(--paper); }
    72   .auth-admin-box-icon { font-size: 1.3rem; }
    73   .auth-admin-box-text { display: flex; flex-direction: column; line-height: 1.3; flex: 1; }
    74   .auth-admin-box-text small { color: var(--ink-muted); font-size: .8rem; }
    75   .auth-admin-box-arrow { color: var(--ink-muted); font-size: 1.1rem; }
    76 </style>
Note: See TracChangeset for help on using the changeset viewer.