Match Live TV
+<%= t("pages.features.hero_title") %>
+<%= t("pages.features.hero_lead") %>
-<%= t("pages.features.staff_section_title") %>
-<%= t("pages.features.staff_invite_title") %>
-<%= t("pages.features.staff_invite_body") %>
+- ">
+
- + + <%= t("pages.features.flow_phone") %> + + +
- + + <%= t("pages.features.flow_account") %> + + +
- + + <%= t("pages.features.flow_live") %> + +
<%= t("pages.features.staff_access_title") %>
-<%= t("pages.features.staff_access_body") %>
+<%= t("pages.features.staff_plans_title") %>
-<%= t("pages.features.staff_plans_body") %>
+<%= t("pages.features.how_title") %>
+-
+
-
+
+
<%= t("pages.features.how_1_title") %>
+<%= t("pages.features.how_1_body") %>
+
+ -
+
+
<%= t("pages.features.how_2_title") %>
+<%= t("pages.features.how_2_body") %>
+
+ -
+
+
<%= t("pages.features.how_3_title") %>
+<%= t("pages.features.how_3_body") %>
+
+ -
+
+
<%= t("pages.features.how_4_title") %>
+<%= t("pages.features.how_4_body") %>
+
+ -
+
+
<%= t("pages.features.how_5_title") %>
+<%= t("pages.features.how_5_body") %>
+
+
<%= t("pages.features.accounts_title") %>
+<%= t("pages.features.accounts_lead") %>
+<%= t("pages.features.accounts_invite_title") %>
+<%= t("pages.features.accounts_invite_body") %>
+<%= t("pages.features.accounts_transmit_title") %>
+<%= t("pages.features.accounts_transmit_body") %>
+<%= t("pages.features.accounts_revoke_title") %>
+<%= t("pages.features.accounts_revoke_body") %>
+<%= t("pages.features.accounts_highlight") %>
+<%= t("pages.features.score_title") %>
+<%= t("pages.features.score_lead") %>
+-
+
- <%= t("pages.features.score_item_app") %> +
- <%= t("pages.features.score_item_signup") %> +
- <%= t("pages.features.score_item_password") %> +
- <%= t("pages.features.score_item_link") %> +
<%= t("pages.features.distribute_title") %>
+<%= t("pages.features.distribute_lead") %>
+- ">
+
- + + <%= t("pages.features.distribute_phone") %> + + +
- + + <%= t("pages.features.distribute_mltv") %> + + +
-
+ + + <%= t("pages.features.distribute_viewers") %> +++ + <%= t("pages.features.distribute_youtube") %> ++
+
<%= t("pages.features.youtube_title") %>
+<%= t("pages.features.youtube_body") %>
+<%= t("pages.features.live_section_title") %>
-<%= t("pages.features.replay_title") %>
+<%= t("pages.features.replay_lead") %>
+-
+
- <%= t("pages.features.replay_item_server") %> +
- <%= t("pages.features.replay_item_plan") %> +
- <%= t("pages.features.replay_item_download") %> +
<%= t("pages.features.more_title") %>
+<%= t("pages.features.live_phone_title") %>
-<%= t("pages.features.live_phone_body") %>
+ +<%= t("pages.features.more_stable_title") %>
+<%= t("pages.features.more_stable_body") %>
<%= t("pages.features.live_score_title") %>
-<%= t("pages.features.live_score_body") %>
+ +<%= t("pages.features.more_link_title") %>
+<%= t("pages.features.more_link_body") %>
<%= t("pages.features.live_link_title") %>
-<%= t("pages.features.live_link_body") %>
-<%= t("pages.features.live_archive_title") %>
-<%= t("pages.features.live_archive_body") %>
-<%= t("pages.features.live_youtube_mltv_title") %>
-<%= t("pages.features.live_youtube_mltv_body") %>
-<%= t("pages.features.live_youtube_club_title") %>
-<%= t("pages.features.live_youtube_club_body") %>
-<%= t("pages.features.live_website_title") %>
-<%= t("pages.features.live_website_body") %>
+ +<%= t("pages.features.more_yt_mltv_title") %>
+<%= t("pages.features.more_yt_mltv_body") %>
- <%= link_to t("pages.features.cta_signup"), public_signup_path, class: "btn btn-primary" %> -
-<%= t("pages.features.cta_title") %>
+<%= t("pages.features.cta_body") %>
+<%= t("home.stores_label") %>
+ <%= render "shared/store_badges", variant: "hero" %> +-
diff --git a/backend/app/views/public/pages/pricing.html.erb b/backend/app/views/public/pages/pricing.html.erb
index 329362e..42b1dca 100644
--- a/backend/app/views/public/pages/pricing.html.erb
+++ b/backend/app/views/public/pages/pricing.html.erb
@@ -22,19 +22,60 @@
<%= render "shared/plan_cards" %>
- +diff --git a/backend/app/views/shared/_marketing_footer.html.erb b/backend/app/views/shared/_marketing_footer.html.erb index 4a61d7d..8992698 100644 --- a/backend/app/views/shared/_marketing_footer.html.erb +++ b/backend/app/views/shared/_marketing_footer.html.erb @@ -2,6 +2,7 @@
+
+ -+ + + + +Free Premium Light Premium Full + -+ Free +Premium Light +Premium Full + +<%= t("pages.pricing.table_staff") %> 1 5 <%= t("pages.pricing.table_unlimited") %> + <%= t("pages.pricing.table_staff") %> +1 +5 +<%= t("pages.pricing.table_unlimited") %> +<%= t("pages.pricing.table_matches") %> 1 3 10 <%= t("pages.pricing.table_live_mltv") %> <%= t("pages.pricing.table_yes") %> <%= t("pages.pricing.table_yes") %> <%= t("pages.pricing.table_yes") %> <%= t("pages.pricing.table_youtube") %> <%= t("pages.pricing.table_no") %> Match Live TV <%= t("pages.pricing.table_youtube_club") %> <%= t("pages.pricing.table_replay") %> <%= t("pages.pricing.table_no") %> <%= t("pages.plans.replay_days", count: 30) %> <%= t("pages.plans.replay_days", count: 90) %> -<%= t("pages.pricing.table_download") %> <%= t("pages.pricing.table_no") %> <%= t("pages.pricing.table_yes") %> <%= t("pages.pricing.table_yes") %> +<%= t("pages.pricing.table_price") %> <%= t("pages.pricing.table_price_free") %> <%= raw t("pages.pricing.table_price_light_html") %> <%= raw t("pages.pricing.table_price_full_html") %> + <%= t("pages.pricing.table_price") %> +<%= t("pages.pricing.table_price_free") %> ++ ++ <%= Billing::Stripe::PriceCatalog.label(plan_slug: "premium_light", interval: "yearly") %> + <%= t("pages.plans.launch_price") %> + <%= t( + "pages.pricing.table_price_note", + list: Billing::Stripe::PriceCatalog.list_label(plan_slug: "premium_light", interval: "yearly"), + monthly: Billing::Stripe::PriceCatalog.label(plan_slug: "premium_light", interval: "monthly") + ) %> +++ ++ <%= Billing::Stripe::PriceCatalog.label(plan_slug: "premium_full", interval: "yearly") %> + <%= t("pages.plans.launch_price") %> + <%= t( + "pages.pricing.table_price_note", + list: Billing::Stripe::PriceCatalog.list_label(plan_slug: "premium_full", interval: "yearly"), + monthly: Billing::Stripe::PriceCatalog.label(plan_slug: "premium_full", interval: "monthly") + ) %> ++Match Live TV — <%= t("footer.tagline") %> + <%= render "shared/store_badges", variant: "footer" %><%= link_to t("common.support"), public_support_path %> · diff --git a/backend/app/views/shared/_plan_cards.html.erb b/backend/app/views/shared/_plan_cards.html.erb index 4759fa2..af93fb9 100644 --- a/backend/app/views/shared/_plan_cards.html.erb +++ b/backend/app/views/shared/_plan_cards.html.erb @@ -15,23 +15,33 @@ current_interval: current_interval, subscription: subscription ) : nil %> -+ <% featured = plan.slug == "premium_full" %> +"> + <% if featured %> +- diff --git a/backend/app/views/shared/_store_badges.html.erb b/backend/app/views/shared/_store_badges.html.erb new file mode 100644 index 0000000..ae198be --- /dev/null +++ b/backend/app/views/shared/_store_badges.html.erb @@ -0,0 +1,31 @@ +<% variant = local_assigns.fetch(:variant, "hero") %> +<%= t("pages.plans.recommended_badge") %>
+ <% end %> <%= render "shared/plan_title", plan: plan %> <% if plan.slug == "free" %>€0- <% elsif plan.slug == "premium_light" %> - <% light_yearly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %> - <% light_monthly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %> -<%= light_yearly %>-<%= raw t("pages.plans.price_alt_html", price: light_monthly) %>
<% else %> - <% full_yearly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %> - <% full_monthly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %> -<%= full_yearly %>-<%= raw t("pages.plans.price_alt_html", price: full_monthly) %>
+ <% yearly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %> + <% monthly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %> + <% list = Billing::Stripe::PriceCatalog.list_label(plan_slug: plan.slug, interval: "yearly") %> + <% equiv = Billing::Stripe::PriceCatalog.equivalent_monthly_label(plan_slug: plan.slug) %> ++ <% if list.present? %> +++ <% end %> +<%= list %>"><%= yearly %>+ <%= t("pages.plans.launch_price") %> + <% if equiv.present? %> +<%= t("pages.plans.price_equiv", price: equiv) %>
+ <% end %> +<%= raw t("pages.plans.price_alt_html", price: monthly) %>
<% end %>-
-
- <%= raw t("pages.plans.staff_html", count: plan.max_staff_transmission || t("pages.plans.staff_unlimited")) %> +
- <%= raw(plan.max_staff_transmission ? t("pages.plans.staff_html", count: plan.max_staff_transmission) : t("pages.plans.staff_unlimited_html")) %>
- <%= raw t("pages.plans.matches_html", count: plan.concurrent_streams_limit || t("pages.plans.matches_unlimited")) %>
- <%= raw t( "pages.plans.replay_html", @@ -48,6 +58,9 @@ end ) %>
<%= t("pages.plans.staff_note_full") %>
+ <% end %> <% if billing_mode %> <% action_kind = action[:kind] %> <% if action_kind == :current %> @@ -78,8 +91,12 @@ <%= link_to t("pages.plans.start_free"), public_signup_path, class: "btn btn-secondary" %> <% else %>- <% plan_intervals_for_display(Billing::Stripe::PriceCatalog.available_intervals(plan_slug: plan.slug)).each do |interval| %> - <%= link_to t("pages.plans.register_with_price", price: Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: interval)), + <% %w[yearly monthly].each do |interval| %> + <%= link_to t( + "billing.messages.activate_button", + plan: plan_cta_name(plan), + price: Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: interval) + ), public_signup_path(plan: plan.slug, interval: interval), class: plan_interval_button_class(interval) %> <% end %> @@ -88,4 +105,3 @@<% end %>+ <%= link_to MatchLiveTv.app_store_url, + class: "store-badge", + target: "_blank", + rel: "noopener noreferrer", + aria: { label: t("home.store_app_store_aria") } do %> + <%= image_tag "/images/store/apple.svg", + alt: "", + class: "store-badge__logo store-badge__logo--apple", + aria: { hidden: true } %> + + <%= t("home.store_app_store_kicker") %> + <%= t("home.store_app_store_name") %> + + <% end %> + <%= link_to MatchLiveTv.play_store_url, + class: "store-badge", + target: "_blank", + rel: "noopener noreferrer", + aria: { label: t("home.store_play_aria") } do %> + <%= image_tag "/images/store/google-play.svg", + alt: "", + class: "store-badge__logo store-badge__logo--play", + aria: { hidden: true } %> + + <%= t("home.store_play_kicker") %> + <%= t("home.store_play_name") %> + + <% end %> +diff --git a/backend/config/initializers/match_live_tv.rb b/backend/config/initializers/match_live_tv.rb index f6b3a0c..155e07c 100644 --- a/backend/config/initializers/match_live_tv.rb +++ b/backend/config/initializers/match_live_tv.rb @@ -180,5 +180,13 @@ module MatchLiveTv def google_analytics_configured? google_analytics_measurement_id.present? end + + def app_store_url + "https://apps.apple.com/us/app/matchlivetv/id6799510705" + end + + def play_store_url + "https://play.google.com/store/apps/details?id=com.matchlivetv.match_live_tv" + end end end diff --git a/backend/config/locales/app.de.yml b/backend/config/locales/app.de.yml index 73d6211..04ebe60 100644 --- a/backend/config/locales/app.de.yml +++ b/backend/config/locales/app.de.yml @@ -82,12 +82,12 @@ de: first_team_name_label: Teamname first_team_name_placeholder: "z. B. Under 15" plan_label: "Starttarif (für den gesamten Verein)" - plan_free: "Free — 1 Übertragungsverantwortlicher pro Team, 1 Livestream" + plan_free: "Free — 1 Streaming-Konto pro Team, 1 Livestream" plan_light: Premium Light plan_full: Premium Full interval_label: Premium-Abrechnung - interval_yearly: "Jährlich — 40 €/Jahr (Light) oder 200 €/Jahr (Full)" - interval_monthly: "Monatlich — 5 €/Monat (Light) oder 20 €/Monat (Full)" + interval_yearly: "Jährlich — 59 €/Jahr (Light) oder 199 €/Jahr (Full)" + interval_monthly: "Monatlich — 7,90 €/Monat (Light) oder 24,90 €/Monat (Full)" submit: Verein erstellen billing: title: "Abonnement — %{name}" @@ -423,6 +423,9 @@ de: upgrade_info_line: "Upgrade (höherer Tarif oder Wechsel zur jährlichen Abrechnung): Die Differenz wird sofort von der gespeicherten Stripe-Karte abgebucht." downgrade_info_line: "Downgrade zwischen kostenpflichtigen Tarifen (z. B. Full → Light): Der neue Tarif gilt ab der nächsten Verlängerung; bis dahin bleiben die Funktionen des aktuellen Tarifs ohne zusätzliche Kosten erhalten." already_requested_line: "Du hast %{plan} bereits ab dem %{when} angefordert: Bis dahin bleibt %{current_plan} aktiv." + prices: + yearly: "%{amount}/Jahr" + monthly: "%{amount}/Monat" regia: meta_title_fallback: "Regie — Match Live TV" title: "Regie — %{home} vs %{away}" diff --git a/backend/config/locales/app.en.yml b/backend/config/locales/app.en.yml index b199909..88f7609 100644 --- a/backend/config/locales/app.en.yml +++ b/backend/config/locales/app.en.yml @@ -77,12 +77,12 @@ en: first_team_name_label: Team name first_team_name_placeholder: "e.g. Under 15" plan_label: "Starting plan (for the whole club)" - plan_free: "Free — 1 broadcast manager per team, 1 live stream" + plan_free: "Free — 1 transmitter account per team, 1 live stream" plan_light: Premium Light plan_full: Premium Full interval_label: Premium billing - interval_yearly: "Yearly — €40/year (Light) or €200/year (Full)" - interval_monthly: "Monthly — €5/month (Light) or €20/month (Full)" + interval_yearly: "Yearly — €59/year (Light) or €199/year (Full)" + interval_monthly: "Monthly — €7.90/month (Light) or €24.90/month (Full)" submit: Create club billing: title: "Subscription — %{name}" @@ -418,6 +418,9 @@ en: upgrade_info_line: "Upgrade (higher plan or switch to annual billing): the difference is charged immediately to the saved Stripe card." downgrade_info_line: "Downgrade between paid plans (e.g. Full → Light): the new plan takes effect from the next renewal; until then the current plan's features remain, with no additional charge now." already_requested_line: "You already requested %{plan} from %{when}: until then %{current_plan} stays active." + prices: + yearly: "%{amount}/year" + monthly: "%{amount}/month" regia: meta_title_fallback: "Control room — Match Live TV" title: "Control room — %{home} vs %{away}" diff --git a/backend/config/locales/app.es.yml b/backend/config/locales/app.es.yml index b4048c3..0b5d0a7 100644 --- a/backend/config/locales/app.es.yml +++ b/backend/config/locales/app.es.yml @@ -82,12 +82,12 @@ es: first_team_name_label: Nombre del equipo first_team_name_placeholder: "ej. Sub-15" plan_label: "Plan inicial (para todo el club)" - plan_free: "Free — 1 responsable de transmisión por equipo, 1 directo" + plan_free: "Free — 1 cuenta de emisión por equipo, 1 directo" plan_light: Premium Light plan_full: Premium Full interval_label: Facturación premium - interval_yearly: "Anual — 40 €/año (Light) o 200 €/año (Full)" - interval_monthly: "Mensual — 5 €/mes (Light) o 20 €/mes (Full)" + interval_yearly: "Anual — 59 €/año (Light) o 199 €/año (Full)" + interval_monthly: "Mensual — 7,90 €/mes (Light) o 24,90 €/mes (Full)" submit: Crear club billing: title: "Suscripción — %{name}" @@ -423,6 +423,9 @@ es: upgrade_info_line: "Mejora (plan superior o cambio a facturación anual): la diferencia se cobra de inmediato en la tarjeta guardada en Stripe." downgrade_info_line: "Downgrade entre planes de pago (p. ej. Full → Light): el nuevo plan se activa desde la próxima renovación; hasta entonces se mantienen las funciones del plan actual, sin cargo adicional ahora." already_requested_line: "Ya has solicitado %{plan} desde el %{when}: hasta entonces sigue activo %{current_plan}." + prices: + yearly: "%{amount}/año" + monthly: "%{amount}/mes" regia: meta_title_fallback: "Control — Match Live TV" title: "Control — %{home} vs %{away}" diff --git a/backend/config/locales/app.fr.yml b/backend/config/locales/app.fr.yml index 0d8b217..751f84c 100644 --- a/backend/config/locales/app.fr.yml +++ b/backend/config/locales/app.fr.yml @@ -82,12 +82,12 @@ fr: first_team_name_label: Nom de l'équipe first_team_name_placeholder: "ex. Under 15" plan_label: "Forfait de départ (pour tout le club)" - plan_free: "Gratuit — 1 responsable diffusion par équipe, 1 direct" + plan_free: "Gratuit — 1 compte diffuseur par équipe, 1 direct" plan_light: Premium Light plan_full: Premium Full interval_label: Facturation premium - interval_yearly: "Annuel — 40 €/an (Light) ou 200 €/an (Full)" - interval_monthly: "Mensuel — 5 €/mois (Light) ou 20 €/mois (Full)" + interval_yearly: "Annuel — 59 €/an (Light) ou 199 €/an (Full)" + interval_monthly: "Mensuel — 7,90 €/mois (Light) ou 24,90 €/mois (Full)" submit: Créer le club billing: title: "Abonnement — %{name}" @@ -423,6 +423,9 @@ fr: upgrade_info_line: "Upgrade (forfait supérieur ou passage à la facturation annuelle) : la différence est débitée immédiatement sur la carte Stripe enregistrée." downgrade_info_line: "Downgrade entre forfaits payants (ex. Full → Light) : le nouveau forfait est actif dès le prochain renouvellement ; jusque-là, les fonctionnalités du forfait actuel restent actives, sans débit supplémentaire pour l'instant." already_requested_line: "Vous avez déjà demandé %{plan} à partir du %{when} : jusque-là, %{current_plan} reste actif." + prices: + yearly: "%{amount}/an" + monthly: "%{amount}/mois" regia: meta_title_fallback: "Régie — Match Live TV" title: "Régie — %{home} vs %{away}" diff --git a/backend/config/locales/app.it.yml b/backend/config/locales/app.it.yml index b6eb872..547ba06 100644 --- a/backend/config/locales/app.it.yml +++ b/backend/config/locales/app.it.yml @@ -77,12 +77,12 @@ it: first_team_name_label: Nome squadra first_team_name_placeholder: "es. Under 15" plan_label: "Piano iniziale (per tutta la società)" - plan_free: "Free — 1 responsabile trasmissione per squadra, 1 live" + plan_free: "Free — 1 account trasmettitore per squadra, 1 live" plan_light: Premium Light plan_full: Premium Full interval_label: Fatturazione premium - interval_yearly: "Annuale — €40/anno (Light) o €200/anno (Full)" - interval_monthly: "Mensile — €5/mese (Light) o €20/mese (Full)" + interval_yearly: "Annuale — €59/anno (Light) o €199/anno (Full)" + interval_monthly: "Mensile — €7,90/mese (Light) o €24,90/mese (Full)" submit: Crea società billing: title: "Abbonamento — %{name}" @@ -418,6 +418,9 @@ it: upgrade_info_line: "Upgrade (piano superiore o passaggio alla fatturazione annuale): la differenza viene addebitata subito sulla carta salvata in Stripe." downgrade_info_line: "Downgrade tra piani a pagamento (es. Full → Light): il nuovo piano è attivo dal prossimo rinnovo; fino ad allora restano le funzioni del piano attuale, senza addebito aggiuntivo ora." already_requested_line: "Hai già richiesto %{plan} dal %{when}: fino ad allora resta attivo %{current_plan}." + prices: + yearly: "%{amount}/anno" + monthly: "%{amount}/mese" regia: meta_title_fallback: "Regia — Match Live TV" title: "Regia — %{home} vs %{away}" diff --git a/backend/config/locales/pages.de.yml b/backend/config/locales/pages.de.yml index f70a940..2d06f2d 100644 --- a/backend/config/locales/pages.de.yml +++ b/backend/config/locales/pages.de.yml @@ -2,32 +2,80 @@ de: pages: features: meta_title: "Streaming-Funktionen für Jugendspiele — Match Live TV" - meta_description: "App für Livestreams vom Handy: Spiel filmen, Spielstand aktualisieren, Link mit Eltern und Großeltern teilen. Spielarchiv und Pläne für Jugend-Sportvereine." - title: Funktionen - lead: "Alles, was nötig ist, damit auch die, die nicht in der Halle sein können, das Spiel verfolgen: einfach für Trainer und Helfer, praktisch für Eltern, Großeltern und Verwandte." - staff_section_title: Für Verein und Team - staff_invite_title: Einladungen mit eigener E-Mail - staff_invite_body: "Der Vorsitzende oder Inhaber lädt ein, wer filmt und wer den Spielstand aktualisiert: jeder meldet sich mit seiner eigenen E-Mail an, ohne Passwörter zu teilen." - staff_access_title: Zugriffsverwaltung - staff_access_body: Sie können einem Helfer den Zugriff entziehen oder eine noch offene Einladung vom Team-Dashboard aus stornieren. - staff_plans_title: Maßgeschneiderte Pläne - staff_plans_body: "Mit Free, Premium Light oder Premium Full erhöhen Sie, wie viele Personen live helfen können und wie viele Spiele parallel laufen können." - live_section_title: Auf dem Platz und live - live_phone_title: Vom Handy filmen - live_phone_body: "Handy aufstellen und den Livestream starten: auch bei schwachem 4G bleibt die Übertragung so stabil wie möglich, ohne Kabelsalat oder Mischpult." - live_score_title: Spielstand vom zweiten Handy - live_score_body: "Ein weiterer Helfer aktualisiert Sätze und Punkte von einem zweiten Handy aus: einfach den Code scannen, der auf dem Bildschirm des Filmenden erscheint." - live_link_title: Link für Familien und Fans - live_link_body: "Teilen Sie einen Link: Eltern, Großeltern und Freunde schauen im Browser von Handy oder Computer, ohne App-Installation." - live_archive_title: Spielarchiv - live_archive_body: "Mit Premium Light oder Full bleiben die Spiele auf dem Server gespeichert (30 oder 90 Tage) und Sie können sie bei Bedarf aufs Handy herunterladen." - live_youtube_mltv_title: Auch auf YouTube (Match Live TV) - live_youtube_mltv_body: "Mit Premium Light ist der Livestream zusätzlich zum Link auf der Website auch auf dem YouTube-Kanal von Match Live TV zu sehen." - live_youtube_club_title: YouTube-Kanal des Vereins - live_youtube_club_body: "Mit Premium Full können Sie denselben Livestream auch auf den offiziellen YouTube-Kanal des Vereins senden." - live_website_title: Auf der Website des Vereins - live_website_body: "Integration zum Einbetten des Players auf der Vereinswebsite — demnächst mit Premium Full." - cta_signup: Team registrieren + meta_description: "Übertragen Sie Vereinsspiele vom Smartphone mit persönlichen Konten, Spielstand per Link, Live auf Match Live TV und YouTube sowie Replay und Archiv. Ohne komplizierte Technik." + hero_title: "Die Livestreams Ihres Vereins. Ohne geteilte Passwörter, ohne komplizierte Technik." + hero_lead: "Laden Sie Trainer, Verantwortliche und Eltern ein, mit dem eigenen Konto zu streamen. Ein Smartphone reicht: Match Live TV übernimmt Zugänge, Spielstand, Live, Replay und Veröffentlichung." + hero_alt: Smartphone auf Stativ für den Livestream und zweites Handy für den Spielstand in der Halle + flow_aria: Ablauf der Übertragung + flow_phone: Ein Smartphone + flow_account: Ein persönliches Konto + flow_live: Sie sind live + how_title: Von der Halle zu den Zuschauern in wenigen Minuten + how_1_title: Spiel anlegen + how_1_body: Der Verein bereitet das Spiel mit Team, Gegner, Datum und Uhrzeit vor. + how_2_title: App öffnen + how_2_body: Die beauftragte Person meldet sich in der Match-Live-TV-App mit dem eigenen persönlichen Konto an. + how_3_title: Livestream starten + how_3_body: Ein Smartphone reicht. Keine spezielle Ausrüstung und keine komplizierte Einrichtung. + how_4_title: Spielstand führen + how_4_body: Eine andere Person kann Ergebnis und Spielstand über einen einfachen Link aktualisieren – ohne App und ohne Konto. + how_5_title: Replay + how_5_body: Nach dem Livestream kann das Spiel gespeichert und als Replay verfügbar gemacht werden – je nach Plan. + accounts_title: Jeder mit dem eigenen Konto. Das Vereins-Passwort bleibt beim Verein. + accounts_lead: Username und Passwort des Hauptkontos müssen nicht mehr geteilt werden. Verantwortliche, Trainer und beauftragte Eltern erhalten einen persönlichen, widerrufbaren Zugang. + accounts_invite_title: Einladen + accounts_invite_body: Der Verein lädt Trainer, Verantwortliche oder Eltern ein, die die Übertragung übernehmen. + accounts_transmit_title: Übertragen + accounts_transmit_body: Jeder meldet sich in der App mit den eigenen Zugangsdaten an. + accounts_revoke_title: Widerrufen + accounts_revoke_body: Wenn jemand nicht mehr mit Team oder Verein zusammenarbeitet, kann der Zugang widerrufen werden, ohne das Passwort der anderen zu ändern. + accounts_highlight: Keine geteilten Passwörter. Jede beauftragte Person hat den eigenen persönlichen Zugang. + score_title: Wer filmt, filmt. Wer den Spielstand führt, führt den Spielstand. + score_lead: Wer überträgt, kann sich auf den Bildausschnitt konzentrieren. Eine andere Person – auch von der Tribüne – öffnet einen einfachen Link und aktualisiert den Spielstand in Echtzeit. + score_item_app: Keine App-Installation + score_item_signup: Keine Registrierung + score_item_password: Kein Passwort + score_item_link: Einfach den geteilten Link öffnen + score_card_aria: Zugang zur Spielstand-Regie + score_card_title: Regie per Link + score_card_body: Wer Sätze und Punkte aktualisiert, öffnet einen geteilten Link. Kein Konto, keine Einladung. + access_link: Zugang per Link + distribute_title: Ein Spiel, mehrere Wege es zu verfolgen + distribute_lead: Vom Smartphone in der Halle erreicht der Livestream die Zuschauer auf Match Live TV und – je nach Plan – auch YouTube. + distribute_aria: Wohin der Livestream geht + distribute_phone: Smartphone + distribute_mltv: Match Live TV + distribute_viewers: Zuschauer + distribute_youtube: YouTube + youtube_badge: Premium Full + youtube_title: Der Livestream bleibt bei Ihrem Verein + youtube_body: Mit Premium Full können Sie auch auf den offiziellen YouTube-Kanal des Vereins streamen. So behält der Verein Publikum, Abonnenten, Aufrufe, Spielarchiv und Sponsorensichtbarkeit. + youtube_mock_channel: Offizieller Vereinskanal + youtube_mock_subs: Abonnenten und Aufrufe des Vereins + replay_title: Das Spiel endet. Die Erinnerung bleibt. + replay_lead: Bei Plänen mit Replay bleiben Spiele auf den Match-Live-TV-Servern gespeichert und können später angesehen werden. Die Aufbewahrungsdauer hängt vom Plan ab. + replay_item_server: Speicherung auf Match-Live-TV-Servern + replay_item_plan: Archivdauer je nach Plan + replay_item_download: Video bei Bedarf aufs Handy herunterladen + replay_card_title: Spielarchiv + replay_card_body: Spiele nach dem Schlusspfiff noch einmal ansehen und bei Bedarf herunterladen. + replay_mock_1_title: Rossi vs Neri + replay_mock_1_meta: "12/05 · 01:25:34" + replay_mock_2_title: Rossi vs Blu + replay_mock_2_meta: "05/05 · 01:18:22" + replay_mock_3_title: Rossi vs Bianchi + replay_mock_3_meta: "28/04 · 01:07:15" + more_title: Auch das gehört zum Produkt + more_stable_title: Livestream vom Smartphone + more_stable_body: Handy aufstellen und Übertragung starten. Auch bei schwachem 4G bleibt der Livestream so stabil wie möglich – ohne Kabel oder Mischpult. + more_link_title: Link für Familien und Fans + more_link_body: "Einen Link teilen: Eltern, Großeltern und Freunde schauen im Browser von Handy oder Computer – ohne App." + more_yt_mltv_title: Auch auf YouTube Match Live TV + more_yt_mltv_body: Der Livestream kann zusätzlich zum Link auf der Website auch auf dem YouTube-Kanal von Match Live TV sichtbar sein. + cta_title: Bereit für das nächste Spiel? + cta_body: Probieren Sie Match Live TV kostenlos. Keine spezielle Ausrüstung nötig. + cta_primary: Kostenlos starten + cta_secondary: Pläne vergleichen pricing: meta_title: "Preise für Jugendspiel-Streaming — Match Live TV" meta_description: "Free-, Premium-Light- und Premium-Full-Pläne für Livestreams und Spielarchiv. Jahresabo für Vereine: mehr Team-Mitglieder, mehr parallele Spiele, Replay und YouTube." @@ -35,8 +83,8 @@ de: club_info_html: "Verein: %{name} · %{billing_link}" manage_subscription: Abo verwalten team_title: Pläne für Ihr Team - team_lead: "Jahresabo für Vereine: die Streaming-Verantwortlichen starten den Livestream aus der App; der Spielstand wird über einen teilbaren Link verwaltet." - table_staff: Streaming-Verantwortliche / Jahr + team_lead: "Jahresabo für Vereine: die Streaming-Konten starten den Livestream aus der App; der Spielstand wird über einen teilbaren Link verwaltet." + table_staff: Streaming-Konten table_matches: Gleichzeitige Spiele table_live_mltv: Live auf Match Live TV table_youtube: YouTube @@ -44,8 +92,7 @@ de: table_download: Handy-Download table_price: Preis table_price_free: "€0" - table_price_light_html: "€40/Jahr oder €5/Monat" - table_price_full_html: "€200/Jahr oder €20/Monat" + table_price_note: "Listenpreis %{list} — %{monthly}" table_yes: Ja table_no: Nein table_unlimited: Unbegrenzt @@ -55,8 +102,16 @@ de: contact_button: Kontaktieren Sie uns plans: price_alt_html: "oder %{price}" - staff_html: "Streaming-Verantwortliche: %{count} / Jahr pro Team" + price_equiv: "entspricht %{price}" + launch_price: Einführungspreis + recommended_badge: Empfohlen für Vereine + cta_name: + premium_light: Light + premium_full: Full + staff_html: "Streaming-Konten: maximal %{count} pro Abonnement" + staff_unlimited_html: "Streaming-Konten: unbegrenzt pro Abonnement" staff_unlimited: unbegrenzt + staff_note_full: "Keine geteilten Passwörter: jede Person nutzt den eigenen Zugang, jederzeit widerrufbar." matches_html: "Spiele: %{count} gleichzeitig (ganzer Verein)" matches_unlimited: unbegrenzt replay_html: "Replay: %{value}" diff --git a/backend/config/locales/pages.en.yml b/backend/config/locales/pages.en.yml index ab92cf8..a843491 100644 --- a/backend/config/locales/pages.en.yml +++ b/backend/config/locales/pages.en.yml @@ -2,32 +2,80 @@ en: pages: features: meta_title: "Youth match streaming features — Match Live TV" - meta_description: "App for live streaming from your phone: film the match, update the score, share the link with parents and grandparents. Match archive and plans for youth sports clubs." - title: Features - lead: "Everything you need so people who can't be at the gym can follow the match: simple for coaches and volunteers, convenient for parents, grandparents and relatives." - staff_section_title: For the club and staff - staff_invite_title: Invites with their own email - staff_invite_body: "The president or owner invites whoever films and whoever updates the score: each person signs in with their own email, no shared passwords." - staff_access_title: Access management - staff_access_body: You can remove a volunteer's access or cancel a pending invite from the team dashboard. - staff_plans_title: Plans to fit your needs - staff_plans_body: With Free, Premium Light or Premium Full you increase how many people can help stream live and how many matches can run in parallel. - live_section_title: On the court and live - live_phone_title: Film from your phone - live_phone_body: "Prop up the phone and start the stream: even with weak 4G the broadcast stays as stable as possible, with no cables or mixers to worry about." - live_score_title: Score from a second phone - live_score_body: "Another volunteer updates sets and points from a second phone: just scan the code shown on the streamer's screen." - live_link_title: Link for families and fans - live_link_body: "Share a link: parents, grandparents and friends watch from their phone or computer browser, no app to install." - live_archive_title: Match archive - live_archive_body: "With Premium Light or Full, matches stay saved on the server (30 or 90 days) and you can download them to your phone whenever you need." - live_youtube_mltv_title: Also on YouTube (Match Live TV) - live_youtube_mltv_body: With Premium Light the stream is also visible on the Match Live TV YouTube channel, in addition to the link on the site. - live_youtube_club_title: Your club's YouTube channel - live_youtube_club_body: With Premium Full you can send the same stream to your club's official YouTube channel. - live_website_title: On the club's website - live_website_body: Integration to embed the player on the club's website — coming with Premium Full. - cta_signup: Register your team + meta_description: "Stream your club's matches from a smartphone with personal accounts, score via link, live on Match Live TV and YouTube, plus replay and archive. No complicated equipment." + hero_title: "Your club's live streams. No shared passwords, no complicated gear." + hero_lead: "Invite coaches, managers and parents to stream with their own account. All you need is a smartphone: Match Live TV handles access, scoring, the live stream, replay and publishing." + hero_alt: Smartphone on a tripod for the live stream and a second phone for the gym scoreboard + flow_aria: Streaming flow + flow_phone: One smartphone + flow_account: One personal account + flow_live: You're live + how_title: From the gym to viewers in a few minutes + how_1_title: Create the match + how_1_body: The club sets up the match with team, opponent, date and time. + how_2_title: Open the app + how_2_body: The assigned person signs into the Match Live TV app with their own personal account. + how_3_title: Start the stream + how_3_body: A smartphone is enough. No dedicated equipment or complicated setup. + how_4_title: Manage the score + how_4_body: Someone else can update the result and score through a simple link, without installing an app or creating an account. + how_5_title: Replay + how_5_body: After the stream ends, the match can be kept and made available as a replay, depending on the plan. + accounts_title: Everyone with their own account. The club password stays with the club. + accounts_lead: You no longer need to share the main club username and password. Managers, coaches and assigned parents get a personal, revocable login. + accounts_invite_title: Invite + accounts_invite_body: The club invites coaches, managers or parents assigned to stream. + accounts_transmit_title: Stream + accounts_transmit_body: Each person signs into the app with their own credentials. + accounts_revoke_title: Revoke + accounts_revoke_body: When someone no longer works with the team or club, their access can be revoked without changing everyone else's password. + accounts_highlight: No shared passwords. Every assigned person has their own personal access. + score_title: Who films, films. Who keeps score, keeps score. + score_lead: The person streaming can focus on the framing. Someone else, even from the stands, can open a simple link and update the score in real time. + score_item_app: No app to install + score_item_signup: No sign-up + score_item_password: No password + score_item_link: Just open the shared link + score_card_aria: Score control access + score_card_title: Score control via a link + score_card_body: Whoever updates sets and points opens a shared link. No account and no invite. + access_link: Open with a link + distribute_title: One match, more ways to follow it + distribute_lead: From the smartphone in the gym, the stream reaches viewers on Match Live TV and, when included in the plan, also on YouTube. + distribute_aria: Where the stream goes + distribute_phone: Smartphone + distribute_mltv: Match Live TV + distribute_viewers: Viewers + distribute_youtube: YouTube + youtube_badge: Premium Full + youtube_title: The stream stays with your club + youtube_body: With Premium Full you can also stream to the club's official YouTube channel. The club keeps its audience, subscribers, views, match archive and sponsor visibility. + youtube_mock_channel: Official club channel + youtube_mock_subs: Club subscribers and views + replay_title: The match ends. The memory stays. + replay_lead: On plans that include replay, matches are stored on Match Live TV servers and can be watched later. How long they are kept depends on the plan. + replay_item_server: Stored on Match Live TV servers + replay_item_plan: Archive length based on the plan + replay_item_download: Download the video to your phone when you need it + replay_card_title: Match archive + replay_card_body: Rewatch matches after the final whistle and download them when you need them. + replay_mock_1_title: Rossi vs Neri + replay_mock_1_meta: "12/05 · 01:25:34" + replay_mock_2_title: Rossi vs Blu + replay_mock_2_meta: "05/05 · 01:18:22" + replay_mock_3_title: Rossi vs Bianchi + replay_mock_3_meta: "28/04 · 01:07:15" + more_title: Also part of the product + more_stable_title: Stream from your smartphone + more_stable_body: Prop up the phone and start streaming. Even with weak 4G the broadcast stays as stable as possible, with no cables or mixers. + more_link_title: Link for families and fans + more_link_body: "Share a link: parents, grandparents and friends watch from their phone or computer browser, no app to install." + more_yt_mltv_title: Also on Match Live TV YouTube + more_yt_mltv_body: You can also make the stream visible on the Match Live TV YouTube channel, in addition to the link on the site. + cta_title: Ready for the next match? + cta_body: Try Match Live TV for free. No dedicated equipment needed. + cta_primary: Start for free + cta_secondary: Compare plans pricing: meta_title: "Youth match streaming pricing — Match Live TV" meta_description: "Free, Premium Light and Premium Full plans for live streaming and match archive. Annual subscription for clubs: more staff, more concurrent matches, replay and YouTube." @@ -35,8 +83,8 @@ en: club_info_html: "Club: %{name} · %{billing_link}" manage_subscription: Manage subscription team_title: Plans for your team - team_lead: "Annual subscription for clubs: streaming staff start the broadcast from the app; the score is managed with a shareable link." - table_staff: Streaming staff / year + team_lead: "Annual subscription for clubs: transmitter accounts start the broadcast from the app; the score is managed with a shareable link." + table_staff: Transmitter accounts table_matches: Concurrent matches table_live_mltv: Live on Match Live TV table_youtube: YouTube @@ -44,8 +92,7 @@ en: table_download: Phone download table_price: Price table_price_free: "€0" - table_price_light_html: "€40/year or €5/month" - table_price_full_html: "€200/year or €20/month" + table_price_note: "list %{list} — %{monthly}" table_yes: "Yes" table_no: "No" table_unlimited: Unlimited @@ -55,8 +102,16 @@ en: contact_button: Contact us plans: price_alt_html: "or %{price}" - staff_html: "Streaming staff: %{count} / year per team" + price_equiv: "equivalent to %{price}" + launch_price: Launch price + recommended_badge: Recommended for clubs + cta_name: + premium_light: Light + premium_full: Full + staff_html: "Transmitter accounts: max %{count} per subscription" + staff_unlimited_html: "Transmitter accounts: unlimited per subscription" staff_unlimited: unlimited + staff_note_full: "No shared passwords: each person uses their own login, which can be revoked at any time." matches_html: "Matches: %{count} concurrent (whole club)" matches_unlimited: unlimited replay_html: "Replay: %{value}" diff --git a/backend/config/locales/pages.es.yml b/backend/config/locales/pages.es.yml index 04ccb88..75b3a82 100644 --- a/backend/config/locales/pages.es.yml +++ b/backend/config/locales/pages.es.yml @@ -2,32 +2,80 @@ es: pages: features: meta_title: "Funciones de streaming de partidos juveniles — Match Live TV" - meta_description: "App para el directo desde el móvil: graba el partido, actualiza el marcador, comparte el enlace con padres y abuelos. Archivo de partidos y planes para clubes deportivos juveniles." - title: Funciones - lead: "Todo lo necesario para que quienes no pueden estar en el pabellón sigan el partido: sencillo para entrenadores y voluntarios, cómodo para padres, abuelos y familiares." - staff_section_title: Para el club y el staff - staff_invite_title: Invitaciones con su propio email - staff_invite_body: "El presidente o propietario invita a quien graba y a quien actualiza el marcador: cada uno accede con su propio email, sin compartir contraseñas." - staff_access_title: Gestión de accesos - staff_access_body: Puedes retirar el acceso a un voluntario o cancelar una invitación pendiente desde el panel del equipo. - staff_plans_title: Planes a medida - staff_plans_body: Con Free, Premium Light o Premium Full aumentas cuántas personas pueden ayudar en directo y cuántos partidos pueden ir en paralelo. - live_section_title: En la cancha y en directo - live_phone_title: Graba desde el móvil - live_phone_body: "Apoyas el móvil e inicias el directo: incluso con 4G débil, la transmisión se mantiene lo más estable posible, sin líos de cables ni mezcladores." - live_score_title: Marcador desde un segundo móvil - live_score_body: "Otro voluntario actualiza sets y puntos desde un segundo móvil: solo hay que escanear el código que aparece en la pantalla de quien graba." - live_link_title: Enlace para familias y aficionados - live_link_body: "Comparte un enlace: padres, abuelos y amigos miran desde el navegador del móvil o del ordenador, sin instalar apps." - live_archive_title: Archivo de partidos - live_archive_body: "Con Premium Light o Full los partidos quedan guardados en el servidor (30 o 90 días) y puedes descargarlos al móvil cuando quieras." - live_youtube_mltv_title: También en YouTube (Match Live TV) - live_youtube_mltv_body: "Con Premium Light el directo también se ve en el canal de YouTube de Match Live TV, además del enlace en el sitio." - live_youtube_club_title: Canal de YouTube del club - live_youtube_club_body: "Con Premium Full puedes enviar el mismo directo también al canal oficial de YouTube del club." - live_website_title: En el sitio web del club - live_website_body: "Integración para incrustar el reproductor en el sitio web del club — próximamente con Premium Full." - cta_signup: Registra tu equipo + meta_description: "Emite los partidos del club desde el móvil con cuentas personales, marcador por enlace, directo en Match Live TV y YouTube, más repetición y archivo. Sin equipo complicado." + hero_title: "Los directos de tu club. Sin contraseñas compartidas, sin equipo complicado." + hero_lead: "Invita a entrenadores, directivos y padres a emitir con su propia cuenta. Basta un smartphone: Match Live TV gestiona accesos, marcador, directo, repetición y publicación." + hero_alt: Smartphone en trípode para el directo y segundo móvil para el marcador en el pabellón + flow_aria: Flujo de la emisión + flow_phone: Un smartphone + flow_account: Una cuenta personal + flow_live: Estás en directo + how_title: Del pabellón a los espectadores en pocos minutos + how_1_title: Crea el partido + how_1_body: El club prepara el partido indicando equipo, rival, fecha y hora. + how_2_title: Abre la app + how_2_body: La persona encargada accede a la app Match Live TV con su propia cuenta personal. + how_3_title: Empieza el directo + how_3_body: Basta el smartphone. Sin equipo dedicado ni configuración complicada. + how_4_title: Gestiona el marcador + how_4_body: Otra persona puede actualizar resultado y marcador con un enlace sencillo, sin instalar apps ni crear una cuenta. + how_5_title: Repetición + how_5_body: Al terminar el directo, el partido puede conservarse y quedar disponible como repetición, según el plan elegido. + accounts_title: Cada uno con su propia cuenta. La contraseña del club se queda en el club. + accounts_lead: Ya no hace falta compartir el usuario y la contraseña de la cuenta principal. Directivos, entrenadores y padres encargados reciben un acceso personal y revocable. + accounts_invite_title: Invita + accounts_invite_body: El club invita a entrenadores, directivos o padres encargados de la emisión. + accounts_transmit_title: Emite + accounts_transmit_body: Cada uno accede a la app con sus propias credenciales. + accounts_revoke_title: Revoca + accounts_revoke_body: Cuando una persona deja de colaborar con el equipo o el club, su acceso puede revocarse sin cambiar la contraseña a los demás. + accounts_highlight: Sin contraseñas compartidas. Cada encargado tiene su propio acceso personal. + score_title: Quien graba, graba. Quien lleva el marcador, lleva el marcador. + score_lead: Quien emite puede centrarse en el encuadre. Otra persona, incluso desde la grada, puede abrir un enlace sencillo y actualizar el marcador en tiempo real. + score_item_app: Sin app que instalar + score_item_signup: Sin registro + score_item_password: Sin contraseña + score_item_link: Basta abrir el enlace compartido + score_card_aria: Acceso a la regia del marcador + score_card_title: Regia con un enlace + score_card_body: Quien actualiza sets y puntos abre un enlace compartido. Sin cuenta y sin invitación. + access_link: Acceso con enlace + distribute_title: Un partido, más formas de seguirlo + distribute_lead: Desde el smartphone en el pabellón, el directo llega a los espectadores en Match Live TV y, según el plan, también a YouTube. + distribute_aria: Dónde llega el directo + distribute_phone: Smartphone + distribute_mltv: Match Live TV + distribute_viewers: Espectadores + distribute_youtube: YouTube + youtube_badge: Premium Full + youtube_title: El directo se queda en tu club + youtube_body: Con Premium Full también puedes emitir en el canal oficial de YouTube del club. Así el club conserva su público, suscriptores, visualizaciones, archivo de partidos y visibilidad de patrocinadores. + youtube_mock_channel: Canal oficial del club + youtube_mock_subs: Suscriptores y visualizaciones del club + replay_title: El partido termina. El recuerdo permanece. + replay_lead: En los planes con repetición, los partidos se guardan en los servidores de Match Live TV y pueden verse después. La duración del archivo depende del plan. + replay_item_server: Conservación en servidores Match Live TV + replay_item_plan: Duración del archivo según el plan + replay_item_download: Descarga del vídeo al móvil cuando haga falta + replay_card_title: Archivo de partidos + replay_card_body: Vuelve a ver los partidos tras el pitido final y descárgalos cuando los necesites. + replay_mock_1_title: Rossi vs Neri + replay_mock_1_meta: "12/05 · 01:25:34" + replay_mock_2_title: Rossi vs Blu + replay_mock_2_meta: "05/05 · 01:18:22" + replay_mock_3_title: Rossi vs Bianchi + replay_mock_3_meta: "28/04 · 01:07:15" + more_title: También forma parte del producto + more_stable_title: Directo desde el smartphone + more_stable_body: Apoyas el móvil e inicias la emisión. Incluso con 4G débil, el directo se mantiene lo más estable posible, sin cables ni mezcladores. + more_link_title: Enlace para familias y aficionados + more_link_body: "Comparte un enlace: padres, abuelos y amigos miran desde el navegador del móvil o del ordenador, sin instalar apps." + more_yt_mltv_title: También en YouTube Match Live TV + more_yt_mltv_body: También puedes hacer visible el directo en el canal de YouTube de Match Live TV, además del enlace en el sitio. + cta_title: ¿Listos para el próximo partido? + cta_body: Prueba Match Live TV gratis. No hace falta equipo dedicado. + cta_primary: Empieza gratis + cta_secondary: Compara los planes pricing: meta_title: "Precios de streaming de partidos juveniles — Match Live TV" meta_description: "Planes Free, Premium Light y Premium Full para directos y archivo de partidos. Suscripción anual para clubes: más staff, más partidos en paralelo, repetición y YouTube." @@ -35,8 +83,8 @@ es: club_info_html: "Club: %{name} · %{billing_link}" manage_subscription: Gestionar suscripción team_title: Planes para tu equipo - team_lead: "Suscripción anual para clubes: los responsables de emisión inician el directo desde la app; el marcador se gestiona con un enlace para compartir." - table_staff: Responsables de emisión / año + team_lead: "Suscripción anual para clubes: las cuentas de emisión inician el directo desde la app; el marcador se gestiona con un enlace para compartir." + table_staff: Cuentas de emisión table_matches: Partidos simultáneos table_live_mltv: Directo en Match Live TV table_youtube: YouTube @@ -44,8 +92,7 @@ es: table_download: Descarga al móvil table_price: Precio table_price_free: "€0" - table_price_light_html: "€40/año o €5/mes" - table_price_full_html: "€200/año o €20/mes" + table_price_note: "tarifa %{list} — %{monthly}" table_yes: "Sí" table_no: "No" table_unlimited: Ilimitado @@ -55,8 +102,16 @@ es: contact_button: Contáctanos plans: price_alt_html: "o %{price}" - staff_html: "Responsables de emisión: %{count} / año por equipo" - staff_unlimited: ilimitado + price_equiv: "equivale a %{price}" + launch_price: Precio de lanzamiento + recommended_badge: Recomendado para clubes + cta_name: + premium_light: Light + premium_full: Full + staff_html: "Cuentas de emisión: máximo %{count} por suscripción" + staff_unlimited_html: "Cuentas de emisión: ilimitadas por suscripción" + staff_unlimited: ilimitadas + staff_note_full: "Sin contraseñas compartidas: cada persona usa su propio acceso, revocable en cualquier momento." matches_html: "Partidos: %{count} simultáneos (todo el club)" matches_unlimited: ilimitados replay_html: "Repetición: %{value}" diff --git a/backend/config/locales/pages.fr.yml b/backend/config/locales/pages.fr.yml index 2783d74..4c807d0 100644 --- a/backend/config/locales/pages.fr.yml +++ b/backend/config/locales/pages.fr.yml @@ -2,32 +2,80 @@ fr: pages: features: meta_title: "Fonctionnalités de streaming des matchs jeunes — Match Live TV" - meta_description: "Application pour le direct depuis le téléphone : filmez le match, mettez à jour le score, partagez le lien avec parents et grands-parents. Archive des matchs et offres pour les clubs sportifs jeunes." - title: Fonctionnalités - lead: "Tout ce qu'il faut pour faire suivre les matchs à ceux qui ne peuvent pas être en salle : simple pour les coachs et bénévoles, pratique pour parents, grands-parents et proches." - staff_section_title: Pour le club et le staff - staff_invite_title: Invitations avec leur propre email - staff_invite_body: "Le président ou le propriétaire invite qui filme et qui met à jour le score : chacun se connecte avec son propre email, sans partager de mot de passe." - staff_access_title: Gestion des accès - staff_access_body: Vous pouvez retirer l'accès à un bénévole ou annuler une invitation encore en attente depuis le tableau de bord de l'équipe. - staff_plans_title: Des offres sur mesure - staff_plans_body: "Avec Free, Premium Light ou Premium Full, augmentez le nombre de personnes qui peuvent aider en direct et le nombre de matchs en parallèle." - live_section_title: Sur le terrain et en direct - live_phone_title: Filmez depuis votre téléphone - live_phone_body: "Posez le téléphone et lancez le direct : même avec un 4G faible, la diffusion reste aussi stable que possible, sans câbles ni mixeur." - live_score_title: Score depuis un second téléphone - live_score_body: "Un autre bénévole met à jour les sets et les points depuis un second téléphone : il suffit de scanner le code affiché sur l'écran de celui qui filme." - live_link_title: Lien pour les familles et supporters - live_link_body: "Partagez un lien : parents, grands-parents et amis regardent depuis le navigateur du téléphone ou de l'ordinateur, sans installer d'application." - live_archive_title: Archive des matchs - live_archive_body: "Avec Premium Light ou Full, les matchs restent enregistrés sur le serveur (30 ou 90 jours) et vous pouvez les télécharger sur le téléphone quand vous voulez." - live_youtube_mltv_title: Aussi sur YouTube (Match Live TV) - live_youtube_mltv_body: "Avec Premium Light, le direct est aussi visible sur la chaîne YouTube Match Live TV, en plus du lien sur le site." - live_youtube_club_title: Chaîne YouTube du club - live_youtube_club_body: "Avec Premium Full, vous pouvez diffuser le même direct sur la chaîne YouTube officielle du club." - live_website_title: Sur le site du club - live_website_body: "Intégration pour intégrer le lecteur sur le site du club — bientôt disponible avec Premium Full." - cta_signup: Inscrire l'équipe + meta_description: "Diffusez les matchs du club depuis un smartphone avec des comptes personnels, le score via un lien, le direct sur Match Live TV et YouTube, plus le replay et l'archive. Sans matériel compliqué." + hero_title: "Les directs de votre club. Sans mots de passe partagés, sans matériel compliqué." + hero_lead: "Invitez entraîneurs, dirigeants et parents à diffuser avec leur propre compte. Un smartphone suffit : Match Live TV gère les accès, le score, le direct, le replay et la publication." + hero_alt: Smartphone sur trépied pour le direct et second téléphone pour le score en salle + flow_aria: Déroulement de la diffusion + flow_phone: Un smartphone + flow_account: Un compte personnel + flow_live: Vous êtes en direct + how_title: De la salle aux spectateurs en quelques minutes + how_1_title: Créez le match + how_1_body: Le club prépare le match en indiquant l'équipe, l'adversaire, la date et l'heure. + how_2_title: Ouvrez l'application + how_2_body: La personne désignée se connecte à l'application Match Live TV avec son propre compte personnel. + how_3_title: Lancez le direct + how_3_body: Un smartphone suffit. Pas de matériel dédié ni de configuration compliquée. + how_4_title: Gérez le score + how_4_body: Une autre personne peut mettre à jour le résultat et le score via un simple lien, sans installer d'application ni créer de compte. + how_5_title: Replay + how_5_body: À la fin du direct, le match peut être conservé et rendu disponible en replay, selon l'offre choisie. + accounts_title: Chacun avec son propre compte. Le mot de passe du club reste au club. + accounts_lead: Plus besoin de partager l'identifiant et le mot de passe du compte principal. Dirigeants, entraîneurs et parents désignés reçoivent un accès personnel et révocable. + accounts_invite_title: Invitez + accounts_invite_body: Le club invite les entraîneurs, dirigeants ou parents chargés de la diffusion. + accounts_transmit_title: Diffusez + accounts_transmit_body: Chacun se connecte à l'application avec ses propres identifiants. + accounts_revoke_title: Révoquez + accounts_revoke_body: Quand une personne ne collabore plus avec l'équipe ou le club, son accès peut être révoqué sans changer le mot de passe des autres. + accounts_highlight: Pas de mots de passe partagés. Chaque personne désignée a son propre accès personnel. + score_title: Qui filme, filme. Qui tient le score, tient le score. + score_lead: La personne qui diffuse peut se concentrer sur le cadrage. Une autre, même depuis les tribunes, peut ouvrir un simple lien et mettre à jour le score en temps réel. + score_item_app: Aucune application à installer + score_item_signup: Aucune inscription + score_item_password: Aucun mot de passe + score_item_link: Il suffit d'ouvrir le lien partagé + score_card_aria: Accès à la régie du score + score_card_title: Régie via un lien + score_card_body: Celui qui met à jour sets et points ouvre un lien partagé. Aucun compte, aucune invitation. + access_link: Accès par lien + distribute_title: Un match, plusieurs façons de le suivre + distribute_lead: Depuis le smartphone en salle, le direct arrive aux spectateurs sur Match Live TV et, selon l'offre, aussi sur YouTube. + distribute_aria: Où va le direct + distribute_phone: Smartphone + distribute_mltv: Match Live TV + distribute_viewers: Spectateurs + distribute_youtube: YouTube + youtube_badge: Premium Full + youtube_title: Le direct reste celui de votre club + youtube_body: Avec Premium Full, vous pouvez aussi diffuser sur la chaîne YouTube officielle du club. Le club conserve ainsi son public, ses abonnés, ses vues, l'archive des matchs et la visibilité des sponsors. + youtube_mock_channel: Chaîne officielle du club + youtube_mock_subs: Abonnés et vues du club + replay_title: Le match se termine. Le souvenir reste. + replay_lead: Sur les offres qui incluent le replay, les matchs restent enregistrés sur les serveurs Match Live TV et peuvent être revus ensuite. La durée de conservation dépend de l'offre. + replay_item_server: Conservation sur les serveurs Match Live TV + replay_item_plan: Durée d'archive selon l'offre + replay_item_download: Téléchargement de la vidéo sur le téléphone si besoin + replay_card_title: Archive des matchs + replay_card_body: Revoyez les matchs après le coup de sifflet final et téléchargez-les quand vous en avez besoin. + replay_mock_1_title: Rossi vs Neri + replay_mock_1_meta: "12/05 · 01:25:34" + replay_mock_2_title: Rossi vs Blu + replay_mock_2_meta: "05/05 · 01:18:22" + replay_mock_3_title: Rossi vs Bianchi + replay_mock_3_meta: "28/04 · 01:07:15" + more_title: Cela fait aussi partie du produit + more_stable_title: Direct depuis le smartphone + more_stable_body: Posez le téléphone et lancez la diffusion. Même avec un 4G faible, le direct reste aussi stable que possible, sans câbles ni mixeur. + more_link_title: Lien pour familles et supporters + more_link_body: "Partagez un lien : parents, grands-parents et amis regardent depuis le navigateur du téléphone ou de l'ordinateur, sans installer d'application." + more_yt_mltv_title: Aussi sur YouTube Match Live TV + more_yt_mltv_body: Vous pouvez aussi rendre le direct visible sur la chaîne YouTube Match Live TV, en plus du lien sur le site. + cta_title: Prêts pour le prochain match ? + cta_body: Essayez Match Live TV gratuitement. Aucun matériel dédié nécessaire. + cta_primary: Commencer gratuitement + cta_secondary: Comparer les offres pricing: meta_title: "Tarifs streaming des matchs jeunes — Match Live TV" meta_description: "Offres Free, Premium Light et Premium Full pour le direct et l'archive des matchs. Abonnement annuel pour les clubs : plus de staff, plus de matchs en parallèle, replay et YouTube." @@ -35,8 +83,8 @@ fr: club_info_html: "Club : %{name} · %{billing_link}" manage_subscription: Gérer l'abonnement team_title: Offres pour votre équipe - team_lead: "Abonnement annuel pour les clubs : les responsables de diffusion démarrent le direct depuis l'application ; le score se gère avec un lien à partager." - table_staff: Responsables de diffusion / an + team_lead: "Abonnement annuel pour les clubs : les comptes diffuseurs démarrent le direct depuis l'application ; le score se gère avec un lien à partager." + table_staff: Comptes diffuseurs table_matches: Matchs simultanés table_live_mltv: Direct sur Match Live TV table_youtube: YouTube @@ -44,8 +92,7 @@ fr: table_download: Téléchargement téléphone table_price: Prix table_price_free: "€0" - table_price_light_html: "€40/an ou €5/mois" - table_price_full_html: "€200/an ou €20/mois" + table_price_note: "tarif %{list} — %{monthly}" table_yes: Oui table_no: Non table_unlimited: Illimité @@ -55,8 +102,16 @@ fr: contact_button: Contactez-nous plans: price_alt_html: "ou %{price}" - staff_html: "Responsables de diffusion : %{count} / an par équipe" - staff_unlimited: illimité + price_equiv: "soit %{price}" + launch_price: Prix de lancement + recommended_badge: Conseillé pour les clubs + cta_name: + premium_light: Light + premium_full: Full + staff_html: "Comptes diffuseurs : maximum %{count} par abonnement" + staff_unlimited_html: "Comptes diffuseurs : illimités par abonnement" + staff_unlimited: illimités + staff_note_full: "Pas de mot de passe partagé : chaque personne utilise son propre accès, révocable à tout moment." matches_html: "Matchs : %{count} simultanés (tout le club)" matches_unlimited: illimités replay_html: "Replay : %{value}" diff --git a/backend/config/locales/pages.it.yml b/backend/config/locales/pages.it.yml index 814ccd1..f2aede9 100644 --- a/backend/config/locales/pages.it.yml +++ b/backend/config/locales/pages.it.yml @@ -2,32 +2,80 @@ it: pages: features: meta_title: "Funzionalità streaming partite giovanili — Match Live TV" - meta_description: "App per diretta live da telefono: filma la partita, aggiorna il punteggio, condividi il link con genitori e nonni. Archivio gare e piani per società sportive giovanili." - title: Funzionalità - lead: "Tutto ciò che serve per far seguire le partite a chi non può essere in palestra: semplice per coach e volontari, comodo per genitori, nonni e parenti." - staff_section_title: Per la società e lo staff - staff_invite_title: Inviti con la propria email - staff_invite_body: "Il presidente o l'owner invita chi filma e chi aggiorna il punteggio: ognuno accede con la sua email, senza condividere password." - staff_access_title: Gestione accessi - staff_access_body: Puoi togliere l'accesso a un volontario o annullare un invito ancora in sospeso dalla dashboard della squadra. - staff_plans_title: Piani su misura - staff_plans_body: Con Free, Premium Light o Premium Full aumenti quante persone possono aiutare in diretta e quante partite possono andare in parallelo. - live_section_title: In campo e in diretta - live_phone_title: Filma dal telefono - live_phone_body: "Appoggi il telefono e avvii la diretta: anche con il 4G debole la trasmissione resta stabile il più possibile, senza impazzire con cavi o mixer." - live_score_title: Punteggio da un secondo telefono - live_score_body: "Un altro volontario aggiorna set e punti da un secondo cellulare: basta inquadrare il codice che compare sullo schermo di chi filma." - live_link_title: Link per famiglie e tifosi - live_link_body: "Condividi un link: genitori, nonni e amici guardano dal browser del telefono o del computer, senza installare app." - live_archive_title: Archivio partite - live_archive_body: "Con Premium Light o Full le gare restano salvate sul server (30 o 90 giorni) e puoi scaricarle sul telefono quando serve." - live_youtube_mltv_title: Anche su YouTube (Match Live TV) - live_youtube_mltv_body: Con Premium Light la diretta è visibile anche sul canale YouTube Match Live TV, oltre al link sul sito. - live_youtube_club_title: Canale YouTube della società - live_youtube_club_body: Con Premium Full puoi mandare la stessa diretta anche sul canale YouTube ufficiale del club. - live_website_title: Sul sito della società - live_website_body: Integrazione per incorporare il player sul sito del club — in arrivo con Premium Full. - cta_signup: Registra la squadra + meta_description: "Trasmetti le partite della società dallo smartphone con account personali, punteggio via link, diretta su Match Live TV e YouTube, replay e archivio. Senza attrezzatura complicata." + hero_title: "Le dirette della tua società. Senza password condivise, senza attrezzatura complicata." + hero_lead: "Invita allenatori, dirigenti e genitori a trasmettere con il proprio account. Basta uno smartphone: Match Live TV gestisce accessi, punteggio, diretta, replay e pubblicazione." + hero_alt: Smartphone su treppiede per la diretta e secondo telefono per il punteggio in palestra + flow_aria: Flusso della trasmissione + flow_phone: Uno smartphone + flow_account: Un account personale + flow_live: Sei in diretta + how_title: Dalla palestra agli spettatori in pochi minuti + how_1_title: Crea la partita + how_1_body: La società prepara la partita indicando squadra, avversario, data e ora. + how_2_title: Apri l'app + how_2_body: L'incaricato accede dall'app Match Live TV con il proprio account personale. + how_3_title: Inizia la diretta + how_3_body: Basta lo smartphone. Nessuna attrezzatura dedicata o configurazione complicata. + how_4_title: Gestisci il punteggio + how_4_body: Un'altra persona può aggiornare risultato e punteggio tramite un semplice link, senza installare app e senza creare un account. + how_5_title: Replay + how_5_body: Al termine della diretta la partita può essere conservata e resa disponibile come replay, in base al piano scelto. + accounts_title: Ognuno con il proprio account. La password della società resta alla società. + accounts_lead: Non serve più condividere username e password dell'account principale. Dirigenti, allenatori e genitori incaricati ricevono un accesso personale e revocabile. + accounts_invite_title: Invita + accounts_invite_body: La società invita allenatori, dirigenti o genitori incaricati della trasmissione. + accounts_transmit_title: Trasmetti + accounts_transmit_body: Ognuno accede all'app con le proprie credenziali personali. + accounts_revoke_title: Revoca + accounts_revoke_body: Quando una persona non collabora più con la squadra o con la società, il suo accesso può essere revocato senza cambiare la password agli altri. + accounts_highlight: Niente password condivise. Ogni incaricato ha il proprio accesso personale. + score_title: Chi filma, filma. Chi tiene il punteggio, tiene il punteggio. + score_lead: La persona che trasmette può concentrarsi sull'inquadratura. Un'altra persona, anche dagli spalti, può aprire un semplice link e aggiornare il punteggio in tempo reale. + score_item_app: Nessuna app da installare + score_item_signup: Nessuna registrazione + score_item_password: Nessuna password + score_item_link: Basta aprire il link condiviso + score_card_aria: Accesso alla regia + score_card_title: Regia con un link + score_card_body: Chi aggiorna set e punti apre un link condiviso. Nessun account e nessun invito. + access_link: Accesso con link + distribute_title: Una partita, più modi di seguirla + distribute_lead: Dallo smartphone in palestra la diretta arriva agli spettatori su Match Live TV e, quando previsto dal piano, anche su YouTube. + distribute_aria: Dove arriva la diretta + distribute_phone: Smartphone + distribute_mltv: Match Live TV + distribute_viewers: Spettatori + distribute_youtube: YouTube + youtube_badge: Premium Full + youtube_title: La diretta resta della tua società + youtube_body: Con Premium Full puoi trasmettere anche sul canale YouTube ufficiale del club. La società mantiene così il proprio pubblico, gli iscritti, le visualizzazioni, l'archivio delle partite e la visibilità degli sponsor. + youtube_mock_channel: Canale ufficiale società + youtube_mock_subs: Iscritti e visualizzazioni del club + replay_title: La partita finisce. Il ricordo resta. + replay_lead: Nei piani che prevedono il replay, le gare restano salvate sui server Match Live TV e possono essere riviste in seguito. La durata della conservazione dipende dal piano. + replay_item_server: Conservazione sui server Match Live TV + replay_item_plan: Durata dell'archivio in base al piano + replay_item_download: Download del video sul telefono quando serve + replay_card_title: Archivio partite + replay_card_body: Rivedi le gare dopo il fischio finale e scaricale quando ti servono. + replay_mock_1_title: Rossi vs Neri + replay_mock_1_meta: "12/05 · 01:25:34" + replay_mock_2_title: Rossi vs Blu + replay_mock_2_meta: "05/05 · 01:18:22" + replay_mock_3_title: Rossi vs Bianchi + replay_mock_3_meta: "28/04 · 01:07:15" + more_title: Anche questo fa parte del prodotto + more_stable_title: Diretta dallo smartphone + more_stable_body: Appoggi il telefono e avvii la trasmissione. Anche con il 4G debole la diretta resta stabile il più possibile, senza cavi o mixer. + more_link_title: Link per famiglie e tifosi + more_link_body: "Condividi un link: genitori, nonni e amici guardano dal browser del telefono o del computer, senza installare app." + more_yt_mltv_title: Anche su YouTube Match Live TV + more_yt_mltv_body: Puoi rendere la diretta visibile anche sul canale YouTube Match Live TV, oltre al link sul sito. + cta_title: Pronti per la prossima partita? + cta_body: Prova Match Live TV gratuitamente. Non serve attrezzatura dedicata. + cta_primary: Inizia gratis + cta_secondary: Confronta i piani pricing: meta_title: "Prezzi streaming partite giovanili — Match Live TV" meta_description: "Piani Free, Premium Light e Premium Full per dirette live e archivio partite. Abbonamento annuale per società: più staff, più partite in parallelo, replay e YouTube." @@ -35,8 +83,8 @@ it: club_info_html: "Società: %{name} · %{billing_link}" manage_subscription: Gestisci abbonamento team_title: Piani per la tua squadra - team_lead: "Abbonamento annuale per società: i responsabili trasmissione avviano la diretta dall'app; il punteggio si gestisce con un link condivisibile." - table_staff: Responsabili trasmissione / anno + team_lead: "Abbonamento annuale per società: gli account trasmettitori avviano la diretta dall'app; il punteggio si gestisce con un link condivisibile." + table_staff: Account trasmettitori table_matches: Partite in contemporanea table_live_mltv: Live su Match Live TV table_youtube: YouTube @@ -44,8 +92,7 @@ it: table_download: Download telefono table_price: Prezzo table_price_free: "€0" - table_price_light_html: "€40/anno o €5/mese" - table_price_full_html: "€200/anno o €20/mese" + table_price_note: "listino %{list} — %{monthly}" table_yes: "Sì" table_no: "No" table_unlimited: Illimitato @@ -55,8 +102,16 @@ it: contact_button: Contattaci plans: price_alt_html: "oppure %{price}" - staff_html: "Responsabili trasmissione: %{count} / anno per squadra" - staff_unlimited: illimitato + price_equiv: "equivale a %{price}" + launch_price: Prezzo lancio + recommended_badge: Consigliato per le società + cta_name: + premium_light: Light + premium_full: Full + staff_html: "Account trasmettitori: massimo %{count} per abbonamento" + staff_unlimited_html: "Account trasmettitori: illimitati per abbonamento" + staff_unlimited: illimitati + staff_note_full: "Niente password condivise: ogni incaricato utilizza il proprio accesso personale, revocabile in qualsiasi momento." matches_html: "Partite: %{count} in contemporanea (tutta la società)" matches_unlimited: illimitate replay_html: "Replay: %{value}" diff --git a/backend/config/locales/ui.de.yml b/backend/config/locales/ui.de.yml index 3f0ff76..5dfb60e 100644 --- a/backend/config/locales/ui.de.yml +++ b/backend/config/locales/ui.de.yml @@ -43,6 +43,13 @@ de: tagline: "Streamen Sie live vom Smartphone. Großeltern, entfernte Verwandte, Freunde — schauen im Browser, ohne Installation. Und wenn Sie es verpasst haben: es ist im Archiv." cta_signup: Team registrieren cta_live: Live-Spiele ansehen + stores_label: Live gehen vom Smartphone + store_app_store_kicker: Laden im + store_app_store_name: App Store + store_app_store_aria: Match Live TV im App Store laden + store_play_kicker: Jetzt bei + store_play_name: Google Play + store_play_aria: Match Live TV bei Google Play laden features_aria: Wichtige Vorteile feature_stable: Stabile Streams feature_archive: Sicheres Archiv diff --git a/backend/config/locales/ui.en.yml b/backend/config/locales/ui.en.yml index 6fabb6c..1a91199 100644 --- a/backend/config/locales/ui.en.yml +++ b/backend/config/locales/ui.en.yml @@ -43,6 +43,13 @@ en: tagline: Stream live from your smartphone. Grandparents, distant relatives, friends — they watch in the browser, with nothing to install. And if you missed it, it's in the archive. cta_signup: Register your team cta_live: Watch live matches + stores_label: To go live from your smartphone + store_app_store_kicker: Download on the + store_app_store_name: App Store + store_app_store_aria: Download Match Live TV on the App Store + store_play_kicker: Get it on + store_play_name: Google Play + store_play_aria: Get Match Live TV on Google Play features_aria: Key benefits feature_stable: Reliable streams feature_archive: Secure archive diff --git a/backend/config/locales/ui.es.yml b/backend/config/locales/ui.es.yml index 7ff839d..280649a 100644 --- a/backend/config/locales/ui.es.yml +++ b/backend/config/locales/ui.es.yml @@ -43,6 +43,13 @@ es: tagline: Emite en directo desde el smartphone. Abuelos, familiares lejanos, amigos — miran en el navegador, sin instalar nada. Y si te lo perdiste, está en el archivo. cta_signup: Registra tu equipo cta_live: Ver directos + stores_label: Para emitir en directo desde el smartphone + store_app_store_kicker: Consíguelo en + store_app_store_name: App Store + store_app_store_aria: Descarga Match Live TV en App Store + store_play_kicker: Disponible en + store_play_name: Google Play + store_play_aria: Descarga Match Live TV en Google Play features_aria: Ventajas principales feature_stable: Directos estables feature_archive: Archivo seguro diff --git a/backend/config/locales/ui.fr.yml b/backend/config/locales/ui.fr.yml index 3b17f06..85c7a34 100644 --- a/backend/config/locales/ui.fr.yml +++ b/backend/config/locales/ui.fr.yml @@ -43,6 +43,13 @@ fr: tagline: Diffusez en direct depuis votre smartphone. Les grands-parents, la famille éloignée, les amis — regardent dans le navigateur, sans rien installer. Et si vous l'avez manqué, c'est dans l'archive. cta_signup: Inscrire votre équipe cta_live: Voir les directs + stores_label: Pour passer en direct depuis le smartphone + store_app_store_kicker: Télécharger dans + store_app_store_name: App Store + store_app_store_aria: Télécharger Match Live TV dans l'App Store + store_play_kicker: Disponible sur + store_play_name: Google Play + store_play_aria: Télécharger Match Live TV sur Google Play features_aria: Avantages principaux feature_stable: Directs stables feature_archive: Archive sécurisée diff --git a/backend/config/locales/ui.it.yml b/backend/config/locales/ui.it.yml index b1fd086..f11b379 100644 --- a/backend/config/locales/ui.it.yml +++ b/backend/config/locales/ui.it.yml @@ -43,6 +43,13 @@ it: tagline: Trasmetti in diretta dallo smartphone. I nonni, i parenti lontani, gli amici — guardano dal browser, senza installare nulla. E se te la sei persa, la ritrovi nell'archivio. cta_signup: Registra la tua squadra cta_live: Guarda le dirette + stores_label: Per andare in diretta dallo smartphone + store_app_store_kicker: Scarica su + store_app_store_name: App Store + store_app_store_aria: Scarica Match Live TV su App Store + store_play_kicker: Disponibile su + store_play_name: Google Play + store_play_aria: Scarica Match Live TV su Google Play features_aria: Vantaggi principali feature_stable: Dirette stabili feature_archive: Archivio sicuro diff --git a/backend/public/images/store/apple.svg b/backend/public/images/store/apple.svg new file mode 100644 index 0000000..52ea4a7 --- /dev/null +++ b/backend/public/images/store/apple.svg @@ -0,0 +1,3 @@ + diff --git a/backend/public/images/store/google-play.svg b/backend/public/images/store/google-play.svg new file mode 100644 index 0000000..93846c7 --- /dev/null +++ b/backend/public/images/store/google-play.svg @@ -0,0 +1,6 @@ + diff --git a/backend/public/marketing.css b/backend/public/marketing.css index 9a80bcb..5272755 100644 --- a/backend/public/marketing.css +++ b/backend/public/marketing.css @@ -1153,7 +1153,84 @@ body.nav-menu-open { overflow: hidden; } flex-wrap: wrap; gap: 12px; justify-content: flex-start; - margin-bottom: 32px; + margin-bottom: 20px; +} +.hero-stores { + margin: 0 0 28px; +} +.hero-stores-label { + margin: 0 0 10px; + font-size: 0.82rem; + font-weight: 600; + color: #9a9aaa; +} +.store-badges { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; +} +.store-badge { + display: inline-flex; + align-items: center; + gap: 10px; + min-height: 48px; + padding: 8px 16px 8px 12px; + border-radius: 10px; + border: 1px solid #3a3a48; + background: #111118; + color: #fff; + text-decoration: none; + line-height: 1.15; +} +.store-badge:hover { + border-color: #6a6a7a; + background: #1c1c26; + color: #fff; + text-decoration: none; +} +.store-badge__logo { + display: block; + width: 24px; + height: 24px; + flex-shrink: 0; + object-fit: contain; +} +.store-badge__logo--play { + width: 22px; + height: 24px; +} +.store-badge__kicker { + display: block; + font-size: 0.58rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #aaa; +} +.store-badge__name { + display: block; + font-size: 0.95rem; + font-weight: 700; + letter-spacing: -0.01em; +} +.store-badges--footer { + margin-top: 12px; +} +.store-badges--footer .store-badge { + min-height: 42px; + padding: 6px 12px 6px 10px; +} +.store-badges--footer .store-badge__logo { + width: 18px; + height: 18px; +} +.store-badges--footer .store-badge__logo--play { + width: 16px; + height: 18px; +} +.store-badges--footer .store-badge__name { + font-size: 0.85rem; } .hero-features { list-style: none; @@ -1288,6 +1365,7 @@ body.nav-menu-open { overflow: hidden; } @media (max-width: 480px) { .hero-split .hero-cta { flex-direction: column; } .hero-split .hero-cta .btn { width: 100%; text-align: center; } + .store-badges--hero .store-badge { flex: 1 1 calc(50% - 5px); justify-content: center; } } .section { padding-top: 40px; @@ -1363,6 +1441,23 @@ body.nav-menu-open { overflow: hidden; } padding: 24px; box-sizing: border-box; } +.plan-card--featured { + border-color: #5a2a2e; + box-shadow: 0 0 0 1px rgba(229, 57, 53, 0.28), 0 10px 28px rgba(0, 0, 0, 0.28); +} +.plan-card__recommend { + display: inline-block; + margin: 0 0 12px; + padding: 5px 10px; + border-radius: 999px; + border: 1px solid rgba(255, 183, 77, 0.45); + background: rgba(255, 183, 77, 0.08); + color: #ffb74d; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.03em; + line-height: 1.35; +} .plan-card h3, .plan-card__title { margin: 0 0 8px; @@ -1389,6 +1484,45 @@ body.nav-menu-open { overflow: hidden; } color: #ffb74d; } .plan-price { font-size: 1.8rem; font-weight: 800; margin: 12px 0; } +.plan-price-block { margin: 8px 0 4px; } +.plan-price-block .plan-price { + margin: 2px 0 8px; + line-height: 1.15; +} +.plan-price--featured { color: #e53935; } +.plan-price-list { + margin: 0 0 2px; + font-size: 0.95rem; + font-weight: 600; + color: #888; +} +.plan-price-list s { + text-decoration: line-through; + text-decoration-thickness: 2px; +} +.plan-price-badge { + display: inline-block; + margin: 0 0 6px; + padding: 3px 8px; + border-radius: 6px; + background: rgba(229, 57, 53, 0.14); + color: #ff8a80; + font-size: 0.68rem; + font-weight: 800; + letter-spacing: 0.06em; + text-transform: uppercase; +} +.plan-price-equiv { + margin: 4px 0 0; + font-size: 0.85rem; + color: #9a9aaa; +} +.plan-staff-note { + margin: -8px 0 16px; + font-size: 0.82rem; + line-height: 1.45; + color: #9a9aaa; +} .plan-card ul { flex: 1 1 auto; padding-left: 18px; @@ -1401,8 +1535,808 @@ body.nav-menu-open { overflow: hidden; } margin-top: auto; } .features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; } -.feature-card { background: #14141c; border-radius: 12px; padding: 20px; border: 1px solid #2a2a36; } +.feature-card { + display: flex; + flex-direction: column; + background: #14141c; + border-radius: 12px; + padding: 22px 20px 20px; + border: 1px solid #2a2a36; + box-sizing: border-box; +} +.feature-card__icon { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + margin: 0 0 14px; + border-radius: 12px; + background: rgba(229, 57, 53, 0.12); + color: #e53935; + font-size: 1.15rem; + line-height: 1; +} +.feature-card__icon--blue { + background: rgba(100, 181, 246, 0.12); + color: #64b5f6; +} +.feature-card__icon--green { + background: rgba(129, 199, 132, 0.12); + color: #81c784; +} +.feature-card__icon--gold { + background: rgba(255, 183, 77, 0.12); + color: #ffb74d; +} +.feature-card__badge { + display: inline-block; + align-self: flex-start; + margin: 0 0 10px; + padding: 3px 8px; + border-radius: 6px; + font-size: 0.68rem; + font-weight: 800; + letter-spacing: 0.05em; + text-transform: uppercase; + line-height: 1.35; +} +.feature-card__badge--invite { + background: rgba(100, 181, 246, 0.12); + color: #64b5f6; +} +.feature-card__badge--link { + background: rgba(129, 199, 132, 0.12); + color: #81c784; +} .feature-card h3 { margin: 0 0 8px; font-size: 1.05rem; } +.feature-card p { margin: 0; color: #aaa; font-size: 0.92rem; line-height: 1.5; } + +/* —— Features page (narrative) —— */ +.features-page { padding-bottom: 24px; } +.features-hero { + padding: 36px 0 32px; + border-bottom: 1px solid #1e1e28; + background: + radial-gradient(ellipse 70% 55% at 50% -10%, rgba(229, 57, 53, 0.16), transparent 60%), + #0a0a0e; +} +.features-hero__grid { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); + gap: 28px 36px; + align-items: center; + padding-bottom: 8px; +} +.features-hero__copy { min-width: 0; } +.features-hero__eyebrow { + margin: 0 0 12px; + color: #e53935; + font-size: 0.8rem; + font-weight: 800; + letter-spacing: 0.08em; + text-transform: uppercase; +} +.features-hero h1 { + margin: 0 0 16px; + max-width: 18ch; + font-size: clamp(1.7rem, 4vw, 2.35rem); + line-height: 1.2; + letter-spacing: -0.02em; +} +.features-hero__lead { + margin: 0 0 24px; + max-width: 42rem; + color: #aaa; + font-size: 1.05rem; + line-height: 1.55; +} +.features-hero__visual { + position: relative; + display: flex; + justify-content: center; + align-items: center; + min-height: 240px; +} +.features-hero__visual::before { + content: ""; + position: absolute; + width: min(380px, 92%); + aspect-ratio: 1; + border-radius: 50%; + background: radial-gradient(circle, rgba(229, 57, 53, 0.22) 0%, transparent 68%); + pointer-events: none; +} +.features-hero__img { + position: relative; + z-index: 1; + width: 100%; + max-width: 480px; + height: auto; + object-fit: contain; + filter: drop-shadow(0 24px 48px rgba(0, 0, 0, 0.55)); +} +.features-flow { + list-style: none; + margin: 0; + padding: 18px 20px; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 10px 8px; + background: #14141c; + border: 1px solid #2a2a36; + border-radius: 14px; + max-width: 720px; +} +.features-flow__item { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + min-width: 7.5rem; + text-align: center; +} +.features-flow__icon { + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 14px; + background: rgba(229, 57, 53, 0.12); + color: #e53935; + font-size: 1.2rem; +} +.features-flow__icon--blue { + background: rgba(100, 181, 246, 0.12); + color: #64b5f6; +} +.features-flow__icon--live { + background: rgba(229, 57, 53, 0.18); +} +.features-flow__dot { + width: 12px; + height: 12px; + border-radius: 50%; + background: #e53935; + box-shadow: 0 0 0 4px rgba(229, 57, 53, 0.25); +} +.features-flow__label { + font-size: 0.88rem; + font-weight: 600; + color: #ddd; + line-height: 1.3; +} +.features-flow__arrow { + color: #555; + font-size: 0.85rem; + padding: 0 4px; +} +.features-panel { + background: #14141c; + border: 1px solid #2a2a36; + border-radius: 14px; + padding: 28px 24px; +} +.features-panel--accent { + border-color: #5a2a2e; + box-shadow: 0 0 0 1px rgba(229, 57, 53, 0.2); +} +.features-panel__lead { + margin: 0 auto 24px; + max-width: 40rem; + color: #aaa; + text-align: center; + line-height: 1.55; +} +.features-panel__lead--left { margin-left: 0; margin-right: 0; text-align: left; } +.features-accounts .features-panel h2 { + text-align: center; +} +.features-accounts__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + margin-bottom: 20px; +} +.features-accounts__card { + background: #101016; + border: 1px solid #2a2a36; + border-radius: 12px; + padding: 18px; +} +.features-accounts__card h3 { + margin: 0 0 8px; + font-size: 1.05rem; +} +.features-accounts__card p { + margin: 0; + color: #aaa; + font-size: 0.92rem; + line-height: 1.5; +} +.features-highlight { + margin: 0; + padding: 14px 16px; + border-radius: 10px; + border: 1px solid rgba(255, 183, 77, 0.35); + background: rgba(255, 183, 77, 0.08); + color: #ffb74d; + text-align: center; + font-weight: 700; + font-size: 0.95rem; + line-height: 1.4; +} +.features-split { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); + gap: 24px; + align-items: stretch; +} +.features-split--reverse { grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr); } +.features-split--reverse .features-split__copy { order: 2; } +.features-split--reverse aside { order: 1; } +.features-split h2 { text-align: left; margin-bottom: 12px; } +.features-checklist { + margin: 0 0 16px; + padding: 0; + list-style: none; +} +.features-checklist li { + position: relative; + padding: 8px 0 8px 28px; + color: #ddd; + border-bottom: 1px solid #222230; +} +.features-checklist li:last-child { border-bottom: none; } +.features-checklist li::before { + content: ""; + position: absolute; + left: 0; + top: 14px; + width: 14px; + height: 14px; + border-radius: 50%; + background: rgba(129, 199, 132, 0.2); + box-shadow: inset 0 0 0 3px #81c784; +} +.features-note { + margin: 0; + color: #888; + font-size: 0.88rem; + line-height: 1.45; +} +.features-score__card, +.features-replay__card { + background: #14141c; + border: 1px solid #2a2a36; + border-radius: 14px; + padding: 0; + display: flex; + flex-direction: column; + justify-content: flex-start; + overflow: hidden; +} +.features-score__visual { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 20px 18px 8px; + background: + radial-gradient(ellipse 80% 70% at 50% 20%, rgba(129, 199, 132, 0.12), transparent 65%), + #101016; + border-bottom: 1px solid #2a2a36; +} +.features-score__phone { + width: min(100%, 220px); + padding: 14px 14px 16px; + border-radius: 18px; + border: 1px solid #2f2f3c; + background: linear-gradient(180deg, #1a1a24 0%, #121218 100%); + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35); +} +.features-score__phone-top { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 0.04em; + color: #9a9aaa; +} +.features-score__live { + display: inline-flex; + align-items: center; + gap: 6px; + color: #81c784; +} +.features-score__live-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: #81c784; + box-shadow: 0 0 0 3px rgba(129, 199, 132, 0.25); +} +.features-score__board { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 12px; +} +.features-score__team { + flex: 1; + text-align: left; +} +.features-score__team--away { text-align: right; } +.features-score__team-name { + display: block; + font-size: 0.72rem; + color: #888; + margin-bottom: 4px; +} +.features-score__points { + display: block; + font-size: 1.7rem; + font-weight: 800; + line-height: 1; + color: #fff; +} +.features-score__sep { + color: #555; + font-size: 1.2rem; + font-weight: 700; + padding-top: 12px; +} +.features-score__actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} +.features-score__btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 0; + border-radius: 8px; + background: rgba(229, 57, 53, 0.9); + color: #fff; + font-size: 0.85rem; + font-weight: 800; +} +.features-score__btn--muted { + background: rgba(100, 181, 246, 0.85); +} +.features-score__linkchip { + display: inline-flex; + align-items: center; + gap: 8px; + max-width: 100%; + padding: 8px 12px; + border-radius: 999px; + border: 1px solid #2f2f3c; + background: #16161e; + color: #c8c8d0; + font-size: 0.75rem; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.features-score__linkchip i { color: #81c784; flex-shrink: 0; } +.features-score__body { + padding: 18px 20px 20px; +} +.features-replay__card { + padding: 0; + justify-content: flex-start; + overflow: hidden; +} +.features-replay__visual { + padding: 16px 16px 10px; + background: + radial-gradient(ellipse 80% 70% at 50% 10%, rgba(255, 183, 77, 0.12), transparent 65%), + #101016; + border-bottom: 1px solid #2a2a36; +} +.features-archive-mock { + background: #16161e; + border: 1px solid #2f2f3c; + border-radius: 12px; + padding: 12px; +} +.features-archive-mock__head { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; + color: #ffb74d; + font-size: 0.82rem; + font-weight: 700; +} +.features-archive-mock__list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 8px; +} +.features-archive-mock__list li { + display: grid; + grid-template-columns: 44px minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + padding: 8px; + border-radius: 8px; + background: #101016; + border: 1px solid #2a2a36; +} +.features-archive-mock__thumb { + width: 44px; + height: 32px; + border-radius: 6px; + background: linear-gradient(135deg, #2a2a36, #1a1a24); +} +.features-archive-mock__thumb--a { + background: linear-gradient(135deg, #3a2a2e, #1e1e28 55%, #e53935); +} +.features-archive-mock__thumb--b { + background: linear-gradient(135deg, #2a3340, #1e1e28 55%, #64b5f6); +} +.features-archive-mock__thumb--c { + background: linear-gradient(135deg, #2e3a2a, #1e1e28 55%, #81c784); +} +.features-archive-mock__info { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} +.features-archive-mock__info strong { + font-size: 0.78rem; + color: #eee; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.features-archive-mock__info em { + font-style: normal; + font-size: 0.68rem; + color: #888; +} +.features-archive-mock__list i { + color: #e53935; + font-size: 0.85rem; +} +.features-replay__body { + padding: 18px 20px 20px; +} +.features-youtube__panel { + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.9fr); + gap: 24px; + align-items: center; + text-align: left; +} +.features-youtube__panel h2 { text-align: left; margin-bottom: 12px; } +.features-youtube__copy .feature-card__badge { margin-bottom: 12px; } +.features-youtube__visual { + display: flex; + justify-content: center; +} +.features-yt-mock { + width: min(100%, 280px); + background: #101016; + border: 1px solid #2f2f3c; + border-radius: 14px; + overflow: hidden; + box-shadow: 0 14px 30px rgba(0, 0, 0, 0.35); +} +.features-yt-mock__player { + position: relative; + display: flex; + align-items: center; + justify-content: center; + aspect-ratio: 16 / 10; + background: + radial-gradient(circle at 30% 40%, rgba(229, 57, 53, 0.35), transparent 45%), + linear-gradient(160deg, #1c1c26, #0e0e14); +} +.features-yt-mock__live { + position: absolute; + top: 10px; + left: 10px; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 8px; + border-radius: 4px; + background: #e53935; + color: #fff; + font-size: 0.65rem; + font-weight: 800; + letter-spacing: 0.06em; +} +.features-yt-mock__dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #fff; +} +.features-yt-mock__play { + width: 52px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 10px; + background: #ff0000; + color: #fff; + font-size: 1.35rem; + box-shadow: 0 8px 20px rgba(255, 0, 0, 0.35); +} +.features-yt-mock__meta { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 14px; + border-top: 1px solid #2a2a36; +} +.features-yt-mock__avatar { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 50%; + background: rgba(229, 57, 53, 0.15); + color: #e53935; + flex-shrink: 0; +} +.features-yt-mock__text { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} +.features-yt-mock__text strong { + font-size: 0.85rem; + color: #fff; +} +.features-yt-mock__text span { + font-size: 0.72rem; + color: #888; +} +.features-score__card-title { + margin: 8px 0 10px; + font-size: 1.15rem; + font-weight: 700; + color: #fff; +} +.features-score__body > p:last-child, +.features-replay__card p:last-child { + margin: 0; + color: #aaa; + line-height: 1.5; +} +.features-how__steps { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 14px; +} +.features-how__step { + background: #14141c; + border: 1px solid #2a2a36; + border-radius: 12px; + padding: 18px 16px; +} +.features-how__num { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + margin-bottom: 10px; + border-radius: 50%; + background: rgba(229, 57, 53, 0.15); + color: #e53935; + font-size: 0.85rem; + font-weight: 800; +} +.features-how__step h3 { + margin: 0 0 8px; + font-size: 1rem; +} +.features-how__step p { + margin: 0; + color: #aaa; + font-size: 0.88rem; + line-height: 1.45; +} +.features-pipeline { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 12px 10px; +} +.features-pipeline__node { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + min-width: 7rem; + padding: 16px 14px; + background: #14141c; + border: 1px solid #2a2a36; + border-radius: 12px; + text-align: center; +} +.features-pipeline__node--hub { + border-color: #5a2a2e; + box-shadow: 0 0 0 1px rgba(229, 57, 53, 0.22); +} +.features-pipeline__icon { + display: flex; + align-items: center; + justify-content: center; + width: 42px; + height: 42px; + border-radius: 12px; + background: rgba(229, 57, 53, 0.12); + color: #e53935; + font-size: 1.1rem; +} +.features-pipeline__icon--blue { + background: rgba(100, 181, 246, 0.12); + color: #64b5f6; +} +.features-pipeline__icon--gold { + background: rgba(255, 183, 77, 0.12); + color: #ffb74d; +} +.features-pipeline__label { + font-size: 0.85rem; + font-weight: 600; + color: #ddd; + line-height: 1.3; +} +.features-pipeline__join { + width: 28px; + height: 2px; + background: linear-gradient(90deg, #333, #e53935, #333); + border-radius: 2px; + flex-shrink: 0; +} +.features-pipeline__targets { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; +} +.features-grid--compact { + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +} +.features-cta__box { + text-align: center; + background: #14141c; + border: 1px solid #2a2a36; + border-radius: 14px; + padding: 36px 24px; +} +.features-cta__box h2 { margin-bottom: 10px; } +.features-cta__box p { + margin: 0 auto 22px; + max-width: 28rem; + color: #aaa; +} +.features-cta__actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: center; +} +.features-cta__actions .btn-primary { + min-width: 10rem; + padding: 12px 22px; + font-weight: 700; +} +.features-cta__actions .btn-outline { + min-width: 10rem; + padding: 11px 20px; + font-weight: 600; + color: #c8c8d0; + border-color: #3a3a48; +} +.features-cta__actions .btn-outline:hover { + color: #fff; + border-color: #6a6a7a; + background: #1c1c26; +} +@media (max-width: 999px) { + .features-how__steps { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .features-accounts__grid { + grid-template-columns: 1fr; + } + .features-split, + .features-split--reverse { + grid-template-columns: 1fr; + } + .features-split--reverse .features-split__copy, + .features-split--reverse aside { order: initial; } + .features-split h2 { text-align: center; } + .features-panel__lead--left { text-align: center; } + .features-checklist { text-align: left; } + .features-hero__grid { + grid-template-columns: 1fr; + gap: 24px; + } + .features-hero h1 { max-width: none; } + .features-hero__visual { order: -1; min-height: 200px; } + .features-hero__img { max-width: 360px; } + .features-youtube__panel { + grid-template-columns: 1fr; + text-align: center; + } + .features-youtube__panel h2 { text-align: center; } + .features-panel__lead--left { text-align: center; } + .features-youtube__copy .feature-card__badge { align-self: center; } + .features-youtube__copy { + display: flex; + flex-direction: column; + align-items: center; + } +} +@media (max-width: 640px) { + .features-hero { padding: 28px 0 20px; } + .features-hero h1 { max-width: none; } + .features-hero__visual { order: 0; min-height: 0; } + .features-hero__img { max-width: 280px; } + .features-flow { + flex-direction: column; + align-items: stretch; + padding: 16px; + } + .features-flow__item { + flex-direction: row; + justify-content: flex-start; + text-align: left; + min-width: 0; + gap: 12px; + } + .features-flow__arrow { + transform: rotate(90deg); + align-self: center; + padding: 2px 0; + } + .features-how__steps { grid-template-columns: 1fr; } + .features-pipeline { flex-direction: column; } + .features-pipeline__join { + width: 2px; + height: 22px; + background: linear-gradient(180deg, #333, #e53935, #333); + } + .features-pipeline__targets { + width: 100%; + flex-direction: column; + } + .features-pipeline__node { width: 100%; } + .features-cta__actions { flex-direction: column; } + .features-cta__actions .btn { width: 100%; text-align: center; } +} .stripe-secure { display: flex; align-items: flex-start; @@ -1437,9 +2371,64 @@ body.nav-menu-open { overflow: hidden; } .site-footer__legal { flex: 1 1 100%; margin-top: 4px; } .site-footer__legal p { margin: 0 0 6px; line-height: 1.45; } .site-footer__legal p:last-child { margin-bottom: 0; } -.compare-table { width: 100%; min-width: 520px; border-collapse: collapse; margin-top: 0; font-size: 0.9rem; } -.compare-table th, .compare-table td { padding: 10px 12px; border-bottom: 1px solid #2a2a36; text-align: left; } -.compare-table th { color: #aaa; font-weight: 600; } +.compare-table-wrap { + margin-top: 28px; +} +.compare-table { + width: 100%; + min-width: 640px; + table-layout: fixed; + border-collapse: collapse; + margin-top: 0; + font-size: 0.9rem; +} +.compare-table__feature { width: 22%; } +.compare-table__plan { width: 26%; } +.compare-table th, +.compare-table td { + padding: 14px 16px; + border-bottom: 1px solid #2a2a36; + vertical-align: middle; +} +.compare-table th:first-child, +.compare-table td:first-child { + text-align: left; + padding-left: 8px; + color: #c8c8d0; + font-weight: 500; +} +.compare-table th:not(:first-child), +.compare-table td:not(:first-child) { + text-align: center; +} +.compare-table th { + color: #ddd; + font-weight: 700; + font-size: 0.95rem; + padding-top: 8px; + padding-bottom: 16px; + border-bottom-color: #3a3a48; +} +.compare-table tbody tr:last-child th, +.compare-table tbody tr:last-child td { + border-bottom: none; + padding-top: 18px; + padding-bottom: 8px; +} +.compare-table td:nth-child(4), +.compare-table th:nth-child(4) { + background: rgba(229, 57, 53, 0.05); +} +@media (max-width: 899px) { + .compare-table th, + .compare-table td { + padding: 12px 10px; + } + .compare-table th:first-child, + .compare-table td:first-child { + padding-left: 4px; + } +} .billing-documents h2 { margin-top: 0; } .billing-table { font-size: 0.9rem; } .billing-table a { white-space: nowrap; } @@ -1632,9 +2621,9 @@ label { display: block; margin-bottom: 6px; font-size: 0.9rem; color: #c8c8d4; f .auth-page .auth-forgot { margin: -4px 0 16px; font-size: 0.88rem; } .plan-price-alt { - color: #aaa; - font-size: 0.9rem; - margin: -8px 0 12px; + color: #888; + font-size: 0.85rem; + margin: 0 0 14px; } .plan-interval-actions { display: flex; @@ -1649,6 +2638,42 @@ label { display: block; margin-bottom: 6px; font-size: 0.9rem; color: #c8c8d4; f padding: 12px 10px; box-sizing: border-box; } +.plan-interval-btn--monthly { + font-size: 0.82rem; + font-weight: 600; + padding: 9px 10px; + color: #c8c8d0; + border-color: #3a3a48; + background: transparent; +} +.plan-interval-btn--monthly:hover { + color: #fff; + border-color: #6a6a7a; + background: #1c1c26; +} +.compare-price { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + line-height: 1.35; + text-align: center; + max-width: 16rem; + margin: 0 auto; +} +.compare-price__launch { + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + color: #ff8a80; +} +.compare-price__note { + display: block; + font-size: 0.78rem; + color: #888; + font-weight: 500; +} .plan-action-hint { color: #888; font-size: 0.85rem; diff --git a/backend/spec/helpers/public/billing_helper_spec.rb b/backend/spec/helpers/public/billing_helper_spec.rb index 731dbcd..6ad9392 100644 --- a/backend/spec/helpers/public/billing_helper_spec.rb +++ b/backend/spec/helpers/public/billing_helper_spec.rb @@ -32,7 +32,7 @@ RSpec.describe Public::BillingHelper, type: :helper do current_interval: "yearly" ) expect(action[:kind]).to eq(:current) - expect(action[:label]).to include("€40/anno") + expect(action[:label]).to include("€59/anno") end it "propone cambio intervallo sullo stesso piano" do diff --git a/backend/spec/requests/public/club_billing_spec.rb b/backend/spec/requests/public/club_billing_spec.rb index 2a2daa2..a897190 100644 --- a/backend/spec/requests/public/club_billing_spec.rb +++ b/backend/spec/requests/public/club_billing_spec.rb @@ -72,7 +72,7 @@ RSpec.describe "Public club billing", type: :request do get public_club_billing_path(club) expect(response.body).to include("Completa dati di fatturazione") - expect(response.body).not_to include("Attiva Premium Light — €40/anno") + expect(response.body).not_to include("Attiva Light — €59/anno") end it "dopo profilo completo reindirizza al checkout" do @@ -111,8 +111,8 @@ RSpec.describe "Public club billing", type: :request do login! get public_club_billing_path(club) - expect(response.body).to include("Premium Light — €40/anno") - expect(response.body).to include("Premium Light — €5/mese") + expect(response.body).to include("Attiva Light — €59/anno") + expect(response.body).to include("Attiva Light — €7,90/mese") allow(Billing::Stripe::CheckoutSession).to receive(:new).and_return( instance_double(Billing::Stripe::CheckoutSession, url: "https://checkout.stripe.com/test") diff --git a/backend/spec/requests/public/features_page_spec.rb b/backend/spec/requests/public/features_page_spec.rb new file mode 100644 index 0000000..02e8d66 --- /dev/null +++ b/backend/spec/requests/public/features_page_spec.rb @@ -0,0 +1,26 @@ +require "rails_helper" + +RSpec.describe "Public features page", type: :request do + it "racconta il prodotto con sezioni narrative e CTA" do + get public_features_path + + expect(response).to have_http_status(:ok) + expect(response.body).to include("Le dirette della tua società") + expect(response.body).to include("Senza password condivise") + expect(response.body).to include("Uno smartphone") + expect(response.body).to include("Un account personale") + expect(response.body).to include("Sei in diretta") + expect(response.body).to include("Dalla palestra agli spettatori") + expect(response.body).to include("Ognuno con il proprio account") + expect(response.body).to include("Niente password condivise") + expect(response.body).to include("Chi filma, filma") + expect(response.body).to include("Accesso con link") + expect(response.body).to include("La diretta resta della tua società") + expect(response.body).to include("La partita finisce. Il ricordo resta.") + expect(response.body).to include("Inizia gratis") + expect(response.body).to include("Confronta i piani") + expect(response.body).to include(public_signup_path) + expect(response.body).to include(public_prezzi_path) + expect(response.body).not_to include("translation missing") + end +end diff --git a/backend/spec/requests/public/home_page_spec.rb b/backend/spec/requests/public/home_page_spec.rb new file mode 100644 index 0000000..a891f82 --- /dev/null +++ b/backend/spec/requests/public/home_page_spec.rb @@ -0,0 +1,41 @@ +require "rails_helper" + +RSpec.describe "Public home page", type: :request do + it "mostra i link agli store nell'hero e nel footer" do + get root_path + + expect(response).to have_http_status(:ok) + expect(response.body).to include(MatchLiveTv.app_store_url) + expect(response.body).to include(MatchLiveTv.play_store_url) + expect(response.body).to include(I18n.t("home.stores_label", locale: :it)) + expect(response.body).to include(I18n.t("home.store_app_store_name", locale: :it)) + expect(response.body).to include(I18n.t("home.store_play_name", locale: :it)) + expect(response.body.scan(MatchLiveTv.app_store_url).size).to eq(3) + expect(response.body.scan(MatchLiveTv.play_store_url).size).to eq(3) + end + + { + "it" => "Per andare in diretta dallo smartphone", + "en" => "To go live from your smartphone", + "fr" => "Pour passer en direct depuis le smartphone", + "de" => "Live gehen vom Smartphone", + "es" => "Para emitir en directo desde el smartphone" + }.each do |locale, label| + it "traduce i badge store in #{locale} senza translation missing" do + cookies[:mltv_locale] = locale + get root_path + + expect(response).to have_http_status(:ok) + expect(response.body).to include(label) + expect(response.body).not_to include("translation missing") + end + end + + it "non mostra i badge store nel chrome App Store Review" do + get public_support_path + + expect(response).to have_http_status(:ok) + expect(response.body).not_to include(MatchLiveTv.app_store_url) + expect(response.body).not_to include(MatchLiveTv.play_store_url) + end +end diff --git a/backend/spec/requests/public/pricing_page_spec.rb b/backend/spec/requests/public/pricing_page_spec.rb new file mode 100644 index 0000000..4d872b9 --- /dev/null +++ b/backend/spec/requests/public/pricing_page_spec.rb @@ -0,0 +1,32 @@ +require "rails_helper" + +RSpec.describe "Public pricing page", type: :request do + before { load Rails.root.join("db/seeds/plans.rb") } + + it "mostra prezzi lancio, listino barrato e Full in evidenza" do + get public_prezzi_path + + expect(response).to have_http_status(:ok) + expect(response.body).to include("€59/anno") + expect(response.body).to include("€79/anno") + expect(response.body).to include("€199/anno") + expect(response.body).to include("€249/anno") + expect(response.body).to include("€7,90/mese") + expect(response.body).to include("€24,90/mese") + expect(response.body).to include("equivale a €4,92/mese") + expect(response.body).to include("equivale a €16,58/mese") + expect(response.body).to include("Prezzo lancio") + expect(response.body).to include("Consigliato per le società") + expect(response.body).to include("Attiva Light — €59/anno") + expect(response.body).to include("Attiva Light — €7,90/mese") + expect(response.body).to include("Attiva Full — €199/anno") + expect(response.body).to include("Attiva Full — €24,90/mese") + expect(response.body).to include("Account trasmettitori: massimo") + expect(response.body).to include("per abbonamento") + expect(response.body).to include("Account trasmettitori") + expect(response.body).to include("Niente password condivise") + expect(response.body).to include("plan-card--featured") + expect(response.body).not_to include("a vita") + expect(response.body).not_to include("Più popolare") + end +end diff --git a/backend/spec/services/billing/payment_description_spec.rb b/backend/spec/services/billing/payment_description_spec.rb index 87275db..bc65dec 100644 --- a/backend/spec/services/billing/payment_description_spec.rb +++ b/backend/spec/services/billing/payment_description_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Billing::PaymentDescription do lines: double(data: []) ) - expect(described_class.for_stripe_invoice(invoice)).to eq("Premium Light — €5/mese") + expect(described_class.for_stripe_invoice(invoice)).to eq("Premium Light — €7,90/mese") end it "ignora descrizioni di test nel pagamento salvato" do @@ -23,6 +23,6 @@ RSpec.describe Billing::PaymentDescription do club: Club.create!(name: "C", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") ) - expect(payment.display_description).to eq("Premium Light — €40/anno") + expect(payment.display_description).to eq("Premium Light — €59/anno") end end diff --git a/backend/spec/services/billing/stripe/price_catalog_spec.rb b/backend/spec/services/billing/stripe/price_catalog_spec.rb index 044e749..115b01c 100644 --- a/backend/spec/services/billing/stripe/price_catalog_spec.rb +++ b/backend/spec/services/billing/stripe/price_catalog_spec.rb @@ -23,6 +23,15 @@ RSpec.describe Billing::Stripe::PriceCatalog do expect { described_class.normalize_interval("weekly") }.to raise_error(ArgumentError) end + it "etichetta il prezzo lancio annuale e il mensile" do + expect(described_class.label(plan_slug: "premium_light", interval: "yearly")).to eq("€59/anno") + expect(described_class.label(plan_slug: "premium_light", interval: "monthly")).to eq("€7,90/mese") + expect(described_class.list_label(plan_slug: "premium_light", interval: "yearly")).to eq("€79/anno") + expect(described_class.equivalent_monthly_label(plan_slug: "premium_light")).to eq("€4,92/mese") + expect(described_class.label(plan_slug: "premium_full", interval: "yearly")).to eq("€199/anno") + expect(described_class.label(plan_slug: "premium_full", interval: "monthly")).to eq("€24,90/mese") + end + it "elenca intervalli configurati" do expect(described_class.available_intervals(plan_slug: "premium_light")).to eq(%w[monthly yearly]) end diff --git a/backend/spec/services/billing/stripe/record_payment_spec.rb b/backend/spec/services/billing/stripe/record_payment_spec.rb index d404155..42909df 100644 --- a/backend/spec/services/billing/stripe/record_payment_spec.rb +++ b/backend/spec/services/billing/stripe/record_payment_spec.rb @@ -38,6 +38,6 @@ RSpec.describe Billing::Stripe::RecordPayment do expect(payment.club).to eq(club) expect(payment.amount_cents).to eq(4000) expect(payment.stripe_invoice_id).to eq("in_test_1") - expect(payment.description).to eq("Premium Light — €40/anno") + expect(payment.description).to eq("Premium Light — €59/anno") end end diff --git a/docs/PRODUCT_PREMIUM.md b/docs/PRODUCT_PREMIUM.md index d635f66..7d003ff 100644 --- a/docs/PRODUCT_PREMIUM.md +++ b/docs/PRODUCT_PREMIUM.md @@ -4,13 +4,14 @@ | | Free | Premium Light | Premium Full | |---|------|---------------|----------------| -| Responsabili trasmissione / anno | 1 | 5 | Illimitato | +| Account trasmettitori | 1 | 5 | Illimitati | | Partite in contemporanea | 1 | 3 | 10 | | Live Match Live TV | Sì | Sì | Sì | | YouTube | No | Match Live TV | Canale società | | Replay server | No | 30 giorni | 90 giorni | | Download telefono | No | Sì | Sì | -| Prezzo | €0 | €40/anno o €5/mese | €200/anno o €20/mese | +| Prezzo lancio | €0 | €59/anno o €7,90/mese | €199/anno o €24,90/mese | +| Listino | — | €79/anno | €249/anno | | Embed sito società | No | No | In arrivo | Il punteggio in diretta si gestisce tramite **link regia** condivisibile (WhatsApp, email, SMS…): non serve un account per chi fa il tabellone. diff --git a/docs/STRIPE_PRODUCTION.md b/docs/STRIPE_PRODUCTION.md index 14d71f3..a885fc6 100644 --- a/docs/STRIPE_PRODUCTION.md +++ b/docs/STRIPE_PRODUCTION.md @@ -5,12 +5,12 @@ ### Prodotti e prezzi Crea **2 prodotti** in Stripe, ciascuno con **2 prezzi ricorrenti**: -| Piano app | Prodotto Stripe (Dashboard) | Mensile | Annuale | Variabile `.env` | +| Piano app | Prodotto Stripe (Dashboard) | Mensile | Annuale (lancio) | Variabile `.env` | |-----------|------------------------------|---------|---------|------------------| -| Premium Light | **Match Live TV** | €5 | €40 | `STRIPE_PREMIUM_LIGHT_MONTHLY_PRICE_ID` / `..._YEARLY_...` | -| Premium Full | **Match Live TV PRO** | €20 | €200 | `STRIPE_PREMIUM_FULL_MONTHLY_PRICE_ID` / `..._YEARLY_...` | +| Premium Light | **Match Live TV** | €7,90 | €59 | `STRIPE_PREMIUM_LIGHT_MONTHLY_PRICE_ID` / `..._YEARLY_...` | +| Premium Full | **Match Live TV PRO** | €24,90 | €199 | `STRIPE_PREMIUM_FULL_MONTHLY_PRICE_ID` / `..._YEARLY_...` | -Gli importi in Stripe Dashboard devono coincidere con il sito (€5/€40 e €20/€200). +Gli importi in Stripe Dashboard devono coincidere con i **prezzi lancio** addebitati sul sito (€7,90/€59 e €24,90/€199). I listini barrati (€79 e €249) sono solo display, non vanno creati come Price Stripe. In Stripe apri ogni prezzo e copia l’**ID prezzo** (`price_...`), non l’ID prodotto. diff --git a/infra/.env.example b/infra/.env.example index bae5327..a7b7fd5 100644 --- a/infra/.env.example +++ b/infra/.env.example @@ -26,6 +26,8 @@ PASSWORD_RESET_EXPIRY_HOURS=2 # Stripe (test: sk_test_... / price_... da Dashboard modalità Test) STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= +# Price ID dei prezzi LANCIO da addebitare: +# Light yearly €59 · Light monthly €7,90 · Full yearly €199 · Full monthly €24,90 # Un price per prodotto e intervallo (Dashboard → Prodotto → Prezzi) STRIPE_PREMIUM_LIGHT_MONTHLY_PRICE_ID= STRIPE_PREMIUM_LIGHT_YEARLY_PRICE_ID=