diff --git a/backend/app/controllers/api/v1/matches_controller.rb b/backend/app/controllers/api/v1/matches_controller.rb
index 464ffd0..e38ed37 100644
--- a/backend/app/controllers/api/v1/matches_controller.rb
+++ b/backend/app/controllers/api/v1/matches_controller.rb
@@ -7,7 +7,7 @@ module Api
def index
matches = @team.matches
.includes(:team, :stream_sessions)
- .order(scheduled_at: :desc)
+ .order(Arel.sql("scheduled_at ASC NULLS LAST"), created_at: :desc)
render json: matches.map { |m| match_json(m) }
end
diff --git a/backend/app/controllers/public/live_controller.rb b/backend/app/controllers/public/live_controller.rb
index 0a1b85b..058f9ce 100644
--- a/backend/app/controllers/public/live_controller.rb
+++ b/backend/app/controllers/public/live_controller.rb
@@ -12,6 +12,15 @@ module Public
.search_by_team_or_opponent(@query)
.order(Arel.sql("started_at DESC NULLS LAST"), created_at: :desc)
+ busy_match_ids = StreamSession.where.not(status: %w[ended error]).select(:match_id)
+ @upcoming_matches = Match
+ .scheduled_upcoming
+ .includes(:team)
+ .search_teams_or_opponents(@query)
+ .where.not(id: busy_match_ids)
+ .order(scheduled_at: :asc)
+ .limit(30)
+
@online_paths = Mediamtx::Client.new.online_path_names
end
diff --git a/backend/app/controllers/public/pages_controller.rb b/backend/app/controllers/public/pages_controller.rb
index 7e919b2..218151b 100644
--- a/backend/app/controllers/public/pages_controller.rb
+++ b/backend/app/controllers/public/pages_controller.rb
@@ -15,5 +15,11 @@ module Public
def terms
end
+
+ def faq
+ end
+
+ def pallavolo
+ end
end
end
diff --git a/backend/app/controllers/public/password_resets_controller.rb b/backend/app/controllers/public/password_resets_controller.rb
new file mode 100644
index 0000000..9bc15af
--- /dev/null
+++ b/backend/app/controllers/public/password_resets_controller.rb
@@ -0,0 +1,52 @@
+module Public
+ class PasswordResetsController < WebBaseController
+ def new
+ end
+
+ def create
+ user = User.find_by(email: params[:email]&.downcase&.strip)
+ if user
+ token = user.generate_password_reset!
+ UserMailer.password_reset(user, token).deliver_now
+ end
+
+ redirect_to public_login_path,
+ notice: "Se l'email è registrata, riceverai a breve un link per reimpostare la password."
+ end
+
+ def edit
+ @user = User.find_by_password_reset_token(params[:token])
+ if @user.nil? || @user.password_reset_expired?
+ redirect_to new_public_password_reset_path,
+ alert: "Link non valido o scaduto. Richiedi un nuovo reset password."
+ return
+ end
+ @token = params[:token]
+ end
+
+ def update
+ @user = User.find_by_password_reset_token(params[:token])
+ if @user.nil? || @user.password_reset_expired?
+ redirect_to new_public_password_reset_path,
+ alert: "Link non valido o scaduto. Richiedi un nuovo reset password."
+ return
+ end
+
+ if params[:password].blank? || params[:password].length < 8
+ flash.now[:alert] = "La password deve avere almeno 8 caratteri"
+ @token = params[:token]
+ return render :edit, status: :unprocessable_entity
+ end
+
+ if params[:password] != params[:password_confirmation]
+ flash.now[:alert] = "Le password non coincidono"
+ @token = params[:token]
+ return render :edit, status: :unprocessable_entity
+ end
+
+ @user.update!(password: params[:password])
+ @user.clear_password_reset!
+ redirect_to public_login_path, notice: "Password aggiornata. Ora puoi accedere."
+ end
+ end
+end
diff --git a/backend/app/controllers/public/registrations_controller.rb b/backend/app/controllers/public/registrations_controller.rb
index 8e23d27..b92b811 100644
--- a/backend/app/controllers/public/registrations_controller.rb
+++ b/backend/app/controllers/public/registrations_controller.rb
@@ -6,6 +6,12 @@ module Public
end
def create
+ unless params[:accept_terms] == "1"
+ @user = User.new(user_params.merge(role: "coach"))
+ flash.now[:alert] = "Devi accettare l’informativa privacy e i termini di servizio per registrarti."
+ return render :new, status: :unprocessable_entity
+ end
+
@user = User.new(user_params.merge(role: "coach"))
if @user.save
session[:user_id] = @user.id
diff --git a/backend/app/controllers/public/site_base_controller.rb b/backend/app/controllers/public/site_base_controller.rb
index bd7ead4..1bce238 100644
--- a/backend/app/controllers/public/site_base_controller.rb
+++ b/backend/app/controllers/public/site_base_controller.rb
@@ -2,6 +2,8 @@ module Public
class SiteBaseController < ActionController::Base
layout "marketing"
+ include ::SeoHelper
+ include ::LegalHelper
helper_method :current_user, :logged_in?
private
diff --git a/backend/app/controllers/public/sitemap_controller.rb b/backend/app/controllers/public/sitemap_controller.rb
new file mode 100644
index 0000000..c9739c6
--- /dev/null
+++ b/backend/app/controllers/public/sitemap_controller.rb
@@ -0,0 +1,21 @@
+module Public
+ class SitemapController < ActionController::Base
+ def show
+ base = MatchLiveTv.app_public_url.chomp("/")
+ @entries = [
+ { loc: "#{base}/", changefreq: "weekly", priority: "1.0" },
+ { loc: "#{base}/funzionalita", changefreq: "monthly", priority: "0.9" },
+ { loc: "#{base}/prezzi", changefreq: "monthly", priority: "0.9" },
+ { loc: "#{base}/pallavolo-giovanile", changefreq: "monthly", priority: "0.85" },
+ { loc: "#{base}/faq", changefreq: "monthly", priority: "0.8" },
+ { loc: "#{base}/live", changefreq: "hourly", priority: "0.85" },
+ { loc: "#{base}/privacy", changefreq: "yearly", priority: "0.3" },
+ { loc: "#{base}/termini", changefreq: "yearly", priority: "0.3" }
+ ]
+
+ respond_to do |format|
+ format.xml { render layout: false }
+ end
+ end
+ end
+end
diff --git a/backend/app/helpers/legal_helper.rb b/backend/app/helpers/legal_helper.rb
new file mode 100644
index 0000000..b1d9db3
--- /dev/null
+++ b/backend/app/helpers/legal_helper.rb
@@ -0,0 +1,5 @@
+module LegalHelper
+ def legal_last_updated
+ "26 maggio 2026"
+ end
+end
diff --git a/backend/app/helpers/public/live_helper.rb b/backend/app/helpers/public/live_helper.rb
index 8ad61e0..0e8ccd4 100644
--- a/backend/app/helpers/public/live_helper.rb
+++ b/backend/app/helpers/public/live_helper.rb
@@ -15,6 +15,24 @@ module Public
partials.map { |p| format_set_partial_entry(p) }.join(" · ")
end
+ def live_scheduled_label(datetime)
+ return nil unless datetime
+
+ datetime.in_time_zone.strftime("%d/%m/%Y alle %H:%M")
+ end
+
+ def live_scheduled_relative(datetime)
+ return nil unless datetime
+
+ if datetime.to_date == Time.zone.today
+ "Oggi alle #{datetime.strftime('%H:%M')}"
+ elsif datetime.to_date == Time.zone.tomorrow
+ "Domani alle #{datetime.strftime('%H:%M')}"
+ else
+ live_scheduled_label(datetime)
+ end
+ end
+
def live_score_points_label(match, score_state)
return "—" unless score_state
diff --git a/backend/app/helpers/seo_helper.rb b/backend/app/helpers/seo_helper.rb
new file mode 100644
index 0000000..bdede25
--- /dev/null
+++ b/backend/app/helpers/seo_helper.rb
@@ -0,0 +1,33 @@
+module SeoHelper
+ DEFAULT_OG_IMAGE_PATH = "/og-share.png"
+
+ def seo_site_url
+ MatchLiveTv.app_public_url.chomp("/")
+ end
+
+ def seo_absolute_url(path)
+ path = "/#{path}" unless path.start_with?("/")
+ "#{seo_site_url}#{path}"
+ end
+
+ def seo_canonical_url
+ if content_for?(:canonical_url)
+ content_for(:canonical_url)
+ else
+ request.original_url.split("?").first
+ end
+ end
+
+ def seo_og_image_url
+ if content_for?(:og_image)
+ img = content_for(:og_image)
+ img.start_with?("http") ? img : seo_absolute_url(img)
+ else
+ seo_absolute_url(DEFAULT_OG_IMAGE_PATH)
+ end
+ end
+
+ def seo_robots_content
+ content_for?(:robots) ? content_for(:robots) : "index, follow"
+ end
+end
diff --git a/backend/app/mailers/application_mailer.rb b/backend/app/mailers/application_mailer.rb
index 3c34c81..e2c7eb2 100644
--- a/backend/app/mailers/application_mailer.rb
+++ b/backend/app/mailers/application_mailer.rb
@@ -1,4 +1,4 @@
class ApplicationMailer < ActionMailer::Base
- default from: "from@example.com"
+ default from: -> { MatchLiveTv.mail_from }
layout "mailer"
end
diff --git a/backend/app/mailers/user_mailer.rb b/backend/app/mailers/user_mailer.rb
new file mode 100644
index 0000000..0d53825
--- /dev/null
+++ b/backend/app/mailers/user_mailer.rb
@@ -0,0 +1,11 @@
+class UserMailer < ApplicationMailer
+ default from: -> { MatchLiveTv.mail_from }
+
+ def password_reset(user, token)
+ @user = user
+ @reset_url = public_password_reset_url(token: token)
+ @expiry_hours = MatchLiveTv.password_reset_expiry_hours
+
+ mail to: user.email, subject: "Reimposta la password — Match Live TV"
+ end
+end
diff --git a/backend/app/models/match.rb b/backend/app/models/match.rb
index cc55889..b67cb52 100644
--- a/backend/app/models/match.rb
+++ b/backend/app/models/match.rb
@@ -2,6 +2,27 @@ class Match < ApplicationRecord
belongs_to :team
has_many :stream_sessions, dependent: :destroy
+ ACTIVE_SESSION_STATUSES = %w[live connecting reconnecting paused].freeze
+
validates :opponent_name, presence: true
validates :sets_to_win, numericality: { greater_than: 0, less_than: 6 }
+
+ scope :scheduled_upcoming, -> {
+ where.not(scheduled_at: nil).where("scheduled_at >= ?", Time.current)
+ }
+
+ scope :search_teams_or_opponents, lambda { |query|
+ q = query.to_s.strip
+ return all if q.blank?
+
+ term = "%#{sanitize_sql_like(q)}%"
+ joins(:team).where(
+ "teams.name ILIKE :term OR matches.opponent_name ILIKE :term",
+ term: term
+ )
+ }
+
+ def broadcast_active?
+ stream_sessions.where(status: ACTIVE_SESSION_STATUSES).exists?
+ end
end
diff --git a/backend/app/models/user.rb b/backend/app/models/user.rb
index 5d6117f..3e00859 100644
--- a/backend/app/models/user.rb
+++ b/backend/app/models/user.rb
@@ -14,4 +14,32 @@ class User < ApplicationRecord
def admin?
role == "admin"
end
+
+ def generate_password_reset!
+ raw = SecureRandom.urlsafe_base64(32)
+ update!(
+ password_reset_digest: User.digest_token(raw),
+ password_reset_sent_at: Time.current
+ )
+ raw
+ end
+
+ def password_reset_expired?
+ password_reset_sent_at.blank? ||
+ password_reset_sent_at < MatchLiveTv.password_reset_expiry_hours.hours.ago
+ end
+
+ def clear_password_reset!
+ update!(password_reset_digest: nil, password_reset_sent_at: nil)
+ end
+
+ def self.find_by_password_reset_token(token)
+ return if token.blank?
+
+ find_by(password_reset_digest: User.digest_token(token))
+ end
+
+ def self.digest_token(token)
+ Digest::SHA256.hexdigest(token.to_s)
+ end
end
diff --git a/backend/app/views/layouts/marketing.html.erb b/backend/app/views/layouts/marketing.html.erb
index 49bd68b..afab5b9 100644
--- a/backend/app/views/layouts/marketing.html.erb
+++ b/backend/app/views/layouts/marketing.html.erb
@@ -6,7 +6,7 @@
<%= content_for?(:title) ? yield(:title) : "Match Live TV" %>
<%= render "shared/meta_tags" %>
-
+
<%= render "shared/marketing_nav" %>
diff --git a/backend/app/views/layouts/marketing_live.html.erb b/backend/app/views/layouts/marketing_live.html.erb
index 9e8a2d5..027cca7 100644
--- a/backend/app/views/layouts/marketing_live.html.erb
+++ b/backend/app/views/layouts/marketing_live.html.erb
@@ -6,8 +6,8 @@
<%= content_for?(:title) ? yield(:title) : "Match Live TV" %>
<%= render "shared/meta_tags" %>
-
-
+
+
<%= yield :head %>
diff --git a/backend/app/views/public/invitations/show.html.erb b/backend/app/views/public/invitations/show.html.erb
index 0f39838..191c53f 100644
--- a/backend/app/views/public/invitations/show.html.erb
+++ b/backend/app/views/public/invitations/show.html.erb
@@ -1,4 +1,6 @@
-<% content_for :title, "Invito squadra" %>
+<% content_for :title, "Invito squadra — Match Live TV" %>
+<% content_for :meta_description, "Accetta l'invito per entrare nello staff della squadra su Match Live TV." %>
+<% content_for :robots, "noindex, nofollow" %>
Accesso staff — <%= @invitation.team.name %>
diff --git a/backend/app/views/public/live/index.html.erb b/backend/app/views/public/live/index.html.erb
index 8e465b6..c68dd3b 100644
--- a/backend/app/views/public/live/index.html.erb
+++ b/backend/app/views/public/live/index.html.erb
@@ -1,76 +1,135 @@
-<% content_for :title do %>Dirette in corso — Match Live TV<% end %>
+<% content_for :title, "Dirette live partite giovanili in corso — Match Live TV" %>
+<% content_for :meta_description, "Guarda le dirette live delle partite giovanili ora in corso e le prossime gare programmate. Cerca la tua squadra e apri il link senza installare app." %>
+<% content_for :canonical_url, seo_absolute_url(public_live_index_path) %>
-
Dirette in corso
-
- Cerca per nome squadra o avversario, poi apri la diretta per guardare lo stream.
-
-
-<%= form_with url: public_live_index_path, method: :get, local: true, class: "search-form" do %>
-
-
- <% if @query.present? %>
- <%= link_to "Azzera", public_live_index_path, class: "btn btn-secondary" %>
- <% end %>
-<% end %>
-
-<% if @query.present? %>
+
Dirette e partite in programma
- Risultati per «<%= h @query %>»: <%= @sessions.size %> diretta<%= @sessions.size == 1 ? "" : "e" %>
+ Cerca la tua squadra: trovi le dirette attive e le partite già programmate dall’app.
-<% end %>
-<% if @sessions.any? %>
-
- <% @sessions.each do |session| %>
- <% match = session.match %>
- <% on_air = @online_paths.include?(session.mediamtx_path_name) %>
-
-
<%= match.team.name %> vs <%= match.opponent_name %>
-
- <% if match.location.present? %><%= match.location %> · <% end %>
- <%= session.platform == "matchlivetv" ? "Match Live TV" : session.platform.capitalize %>
-
+ <% end %>
+
+ <% if @sessions.empty? %>
+ <% if @upcoming_matches.any? %>
+
+
Nessuna diretta in questo momento.
+
Torna all’orario indicato: quando la squadra avvierà lo streaming, la partita comparirà in «In diretta adesso».
+
+ <% else %>
+
+
+
Nessuna diretta adesso — ma puoi già organizzarti
+
+ Se la tua squadra usa Match Live TV, dal telefono si programma la partita con data e ora.
+ Genitori e parenti trovano qui la diretta quando il fischio d’inizio è vicino.
+
+
+
Programma la gara dall’app (senza avviare subito la diretta)
+
La partita compare in «Prossime partite» con orario e campo
+
Lo streaming parte solo quando premi avvio in palestra
+
+
+ <%= link_to "Registra la tua squadra", public_signup_path, class: "btn btn-primary" %>
+ <%= link_to "Scopri come funziona", public_features_path, class: "btn btn-secondary" %>
+
+
+
+
+ <% end %>
+ <% end %>
diff --git a/backend/app/views/public/live/show.html.erb b/backend/app/views/public/live/show.html.erb
index 1a3db06..f689425 100644
--- a/backend/app/views/public/live/show.html.erb
+++ b/backend/app/views/public/live/show.html.erb
@@ -1,4 +1,6 @@
-<% content_for :title do %><%= @match.team.name %> vs <%= @match.opponent_name %> — Match Live TV<% end %>
+<% content_for :title do %><%= @match.team.name %> vs <%= @match.opponent_name %> — Diretta live<% end %>
+<% content_for :meta_description do %>Segui in diretta live <%= @match.team.name %> vs <%= @match.opponent_name %><% if @match.location.present? %> — <%= @match.location %><% end %>. Guarda dal browser senza installare app.<% end %>
+<% content_for :robots, "noindex, follow" %>
<% content_for :head do %>
<% unless @stream_closed %>
diff --git a/backend/app/views/public/pages/faq.html.erb b/backend/app/views/public/pages/faq.html.erb
new file mode 100644
index 0000000..59a84d4
--- /dev/null
+++ b/backend/app/views/public/pages/faq.html.erb
@@ -0,0 +1,83 @@
+<% content_for :title, "FAQ — Diretta live partite giovanili | Match Live TV" %>
+<% content_for :meta_description, "Domande frequenti su streaming partite giovanili: come guardare la diretta senza app, costi, archivio, pallavolo Under 16, inviti staff e piani Free e Premium." %>
+<% content_for :canonical_url, seo_absolute_url(public_faq_path) %>
+
+
+
Domande frequenti sullo streaming delle partite giovanili
+
+ Risposte per genitori, allenatori e dirigenti che vogliono mandare in diretta live le partite
+ di pallavolo, calcio e altri sport dilettantistici.
+
+
+
+
+ Come guardo la partita di mio figlio senza installare app?
+
+ La società condivide un link (via WhatsApp, email o gruppo squadra). Apri il link dal telefono o dal computer:
+ vedi la diretta live nel browser, senza registrarti. Funziona per nonni, parenti e amici lontani.
+
+
+
+
+ Chi può avviare la diretta dalla palestra?
+
+ Coach, dirigenti o volontari invitati dalla società. Usano l’app Match Live TV sul telefono per filmare e,
+ se serve, un secondo telefono per aggiornare il punteggio. La diretta parte solo quando premo avvio in palestra.
+
+
+
+
+ È adatto alla pallavolo giovanile (Under 14, Under 16)?
+
+ Sì. Il punteggio a set è pensato per la pallavolo; puoi personalizzare regole per tornei particolari.
+ Scopri la pagina dedicata: <%= link_to "Match Live TV per pallavolo giovanile", public_pallavolo_path %>.
+
+
+
+
+ Cosa succede se perdo la diretta?
+
+ Con i piani Premium Light o Full la partita viene salvata in archivio (30 o 90 giorni) e puoi rivederla dal sito.
+ Il piano Free non include replay su server, ma la diretta resta gratuita per chi trasmette e per chi guarda.
+
+
+
+
+ Quanto costa per la società?
+
+ Puoi iniziare con il piano Free (limiti su staff e una diretta alla volta).
+ Premium Light e Full aggiungono più partite in parallelo, archivio e YouTube.
+ <%= link_to "Confronta i prezzi", public_prezzi_path %>.
+
+
+
+
+ Posso programmare una partita prima del giorno gara?
+
+ Sì, dall’app si imposta data e ora: la partita compare in «Prossime partite» sul sito.
+ Lo streaming parte solo quando lo staff avvia la trasmissione in palestra.
+
+
+
+
+ La diretta va anche su YouTube?
+
+ Con Premium Light puoi mandarla sul canale Match TV Light; con Premium Full anche sul canale YouTube della società.
+ Chi preferisce resta sul link Match Live TV, comodo per le famiglie.
+
+
+
+
+ Devo aprire porte sul router o avere attrezzatura da TV?
+
+ No per chi guarda. Per la società che trasmette basta lo smartphone e una buona connessione in palestra;
+ lo staff tecnico della piattaforma gestisce l’infrastruttura. Niente camere broadcast o mixer.
+
diff --git a/backend/app/views/public/pages/features.html.erb b/backend/app/views/public/pages/features.html.erb
index 7cd3c5a..629e394 100644
--- a/backend/app/views/public/pages/features.html.erb
+++ b/backend/app/views/public/pages/features.html.erb
@@ -1,24 +1,25 @@
-<% content_for :title, "Funzionalità — Match Live TV" %>
-<% content_for :meta_description, "Camera, regia, punteggio live, gestione staff, archivio gare e YouTube con Premium Light e Full." %>
+<% content_for :title, "Funzionalità streaming partite giovanili — Match Live TV" %>
+<% content_for :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." %>
+<% content_for :canonical_url, seo_absolute_url(public_features_path) %>
Funzionalità
-
Tutto ciò che serve per trasmettere partite giovanili in modo professionale, senza complessità da broadcaster TV.
+
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.
Per la società e lo staff
-
Staff con email
-
Delega trasmissione e regia a coach e volontari: ognuno ha credenziali personali.
+
Inviti con la propria email
+
Il presidente o l’owner invita chi filma e chi aggiorna il punteggio: ognuno accede con la sua email, senza condividere password.
-
Revoca accessi
-
L'owner rimuove membri dello staff e annulla richieste pendenti dalla dashboard web.
+
Gestione accessi
+
Puoi togliere l’accesso a un volontario o annullare un invito ancora in sospeso dalla dashboard della squadra.
-
Limiti per piano
-
Posti staff (trasmissione e regia) e partite concorrenti scalano con Free, Premium Light e Premium Full.
+
Piani su misura
+
Con Free, Premium Light o Premium Full aumenti quante persone possono aiutare in diretta e quante partite possono andare in parallelo.
@@ -27,32 +28,32 @@
In campo e in diretta
-
Modalità camera
-
Streaming RTMP resiliente: la diretta continua anche con reti instabili.
+
Filma dal telefono
+
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.
-
Regia separata
-
Secondo dispositivo per aggiornare il punteggio via QR pairing.
+
Punteggio da un secondo telefono
+
Un altro volontario aggiorna set e punti da un secondo cellulare: basta inquadrare il codice che compare sullo schermo di chi filma.
-
Pagina pubblica /live
-
Genitori e tifosi guardano dal browser senza installare nulla.
+
Link per famiglie e tifosi
+
Condividi un link: genitori, nonni e amici guardano dal browser del telefono o del computer, senza installare app.
-
Archivio replay
-
Premium Light/Full: salvataggio server (30 o 90 giorni) e download su telefono.
+
Archivio partite
+
Con Premium Light o Full le gare restano salvate sul server (30 o 90 giorni) e puoi scaricarle sul telefono quando serve.
-
YouTube Match TV Light
-
Premium Light: le dirette sono visibili anche sul canale YouTube Match TV Light.
+
Anche su YouTube (Match TV Light)
+
Con Premium Light la diretta è visibile anche sul canale YouTube Match TV Light, oltre al link Match Live TV.
-
YouTube della società
-
Premium Full: relay verso il canale YouTube del club.
+
Canale YouTube della società
+
Con Premium Full puoi mandare la stessa diretta anche sul canale YouTube ufficiale del club.
-
Sito della società
-
Integrazione embed sul sito del club — in arrivo con Premium Full.
+
Sul sito della società
+
Integrazione per incorporare il player sul sito del club — in arrivo con Premium Full.
diff --git a/backend/app/views/public/pages/home.html.erb b/backend/app/views/public/pages/home.html.erb
index 7e0e541..aad71cd 100644
--- a/backend/app/views/public/pages/home.html.erb
+++ b/backend/app/views/public/pages/home.html.erb
@@ -1,5 +1,6 @@
-<% content_for :title, "Match Live TV — Ogni partita, ogni evento, per chi non può esserci" %>
-<% content_for :meta_description, "Trasmetti le partite dallo smartphone. Nonni, parenti e amici guardano dal browser, senza app. Se te la perdi, la ritrovi nell'archivio." %>
+<% content_for :title, "Match Live TV — Diretta live partite giovanili da telefono" %>
+<% content_for :meta_description, "Non puoi andare in palestra? Guarda la diretta live della partita di tuo figlio dal telefono o dal computer, senza installare app. Pallavolo, calcio e sport giovanili: link da condividere con nonni e parenti, archivio se te la perdi." %>
+<% content_for :canonical_url, seo_absolute_url(root_path) %>
@@ -41,7 +42,7 @@
-
Come funziona
+
Come funziona lo streaming per squadre giovanili
@@ -79,10 +80,30 @@
-
Piani per ogni esigenza
+
Piani per dirette live e archivio partite
Inizia gratis. Passa a Premium quando vuoi più partite in contemporanea, archivio più lungo e diretta anche su YouTube.
<%= image_tag "/home-piani-ecosistema.png?v=1", alt: "Ecosistema Match Live TV: smartphone in diretta, laptop con live e statistiche, tablet con archivio partite e cloud", class: "plans-teaser-img", loading: "lazy" %>
Streaming partite giovanili: perché le famiglie scelgono Match Live TV
+
+ Allenatori e dirigenti cercano un modo semplice per mandare in diretta live le partite
+ di pallavolo giovanile, calcio, basket e altri sport: senza attrezzature da TV,
+ solo con lo smartphone in palestra. Match Live TV è pensato per le società dilettantistiche che vogliono
+ far guardare Under 14, Under 16, Under 18 e settori giovanili a chi è lontano.
+
+
+ Un genitore che lavora, un nonno che non può viaggiare, un parente all’estero: aprono un link e seguono
+ la partita dal browser. Se arrivano in ritardo, la registrazione in archivio (con i piani Premium)
+ resta disponibile per giorni. Niente account complicati per chi guarda — solo per lo staff che trasmette.
+
+
+ Hai dubbi su come funziona? Leggi le <%= link_to "domande frequenti", public_faq_path %>,
+ scopri <%= link_to "Match Live TV per la pallavolo giovanile", public_pallavolo_path %>
+ o <%= link_to "registra la squadra gratis", public_signup_path %>.
+
+
diff --git a/backend/app/views/public/pages/pallavolo.html.erb b/backend/app/views/public/pages/pallavolo.html.erb
new file mode 100644
index 0000000..b636f0e
--- /dev/null
+++ b/backend/app/views/public/pages/pallavolo.html.erb
@@ -0,0 +1,67 @@
+<% content_for :title, "Diretta live pallavolo giovanile — Under 14, Under 16 | Match Live TV" %>
+<% content_for :meta_description, "Streaming partite di pallavolo giovanile da telefono: diretta live per genitori e nonni, punteggio a set, link da condividere. Ideale per società Under 14, Under 16 e Under 18." %>
+<% content_for :canonical_url, seo_absolute_url(public_pallavolo_path) %>
+
+
+
Match Live TV per la pallavolo giovanile
+
+ La soluzione per società, coach e genitori che vogliono una diretta live affidabile
+ delle partite Under 14, Under 16, Under 18 e settori giovanili — senza diventare telecronisti.
+
+
+
+
Perché la pallavolo giovanile ha bisogno di uno streaming semplice
+
+ In palestra i genitori non sono sempre tutti in tribuna: turni di lavoro, fratelli piccoli, trasferte.
+ Un nonno in un’altra città o un parente all’estero vuole comunque vedere il tie-break del terzo set.
+ Match Live TV nasce per questo: streaming partite pallavolo giovanile dal telefono,
+ con punteggio aggiornato e link immediato da mandare su WhatsApp.
+
+
+
Cosa fa il coach in due minuti
+
+
Programma la partita con data, avversario e campo dall’app
+
Il giorno gara appoggia il telefono e avvia la diretta
+
Un volontario può aggiornare set e punti da un secondo cellulare
+
Condivide il link con il gruppo genitori: tutti guardano dal browser
+
+
+
Cosa vede la famiglia a casa
+
+ Nessuna app da installare. Apri il link e segui la partita in diretta live: nome squadre, punteggio,
+ stato del set. Se hai perso l’inizio, con Premium la gara resta in archivio per rivederla quando vuoi.
+
+
+
Pallavolo: punteggio e regole
+
+ Set da vincere 2 o 3, punti 25/21/15 configurabili per tornei non standard. La regia è separata dalla ripresa:
+ in campo resta concentrato sul gioco, non sul telefono.
+
+
+
Altri sport giovanili
+
+ La stessa piattaforma funziona anche per calcio, basket e sport di squadra dilettantistici.
+ La pallavolo resta lo sport con cui molte società iniziano perché il punteggio a set è già integrato.
+
+
+
+
+
+
Genitori e nonni
+
Link su WhatsApp, visione da telefono o TV con browser. Zero registrazione per chi guarda.
+
+
+
Società e staff
+
Inviti con email, più operatori con Premium, revoca accessi dalla dashboard web.
+
+
+
Archivio gare
+
Premium Light/Full: replay per chi ha perso la diretta o vuole rivedere un punto chiave.
diff --git a/backend/app/views/public/pages/pricing.html.erb b/backend/app/views/public/pages/pricing.html.erb
index bb20003..ede03b0 100644
--- a/backend/app/views/public/pages/pricing.html.erb
+++ b/backend/app/views/public/pages/pricing.html.erb
@@ -1,4 +1,6 @@
-<% content_for :title, "Prezzi — Match Live TV" %>
+<% content_for :title, "Prezzi streaming partite giovanili — Match Live TV" %>
+<% content_for :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." %>
+<% content_for :canonical_url, seo_absolute_url(public_prezzi_path) %>
Piani per la tua squadra
diff --git a/backend/app/views/public/pages/privacy.html.erb b/backend/app/views/public/pages/privacy.html.erb
index da3c60d..1c61be3 100644
--- a/backend/app/views/public/pages/privacy.html.erb
+++ b/backend/app/views/public/pages/privacy.html.erb
@@ -1,9 +1,188 @@
-<% content_for :title, "Privacy — Match Live TV" %>
-
-
Informativa privacy
-
-
Match Live TV tratta i dati necessari per account, gestione staff e streaming (email, nome, dati di utilizzo delle sessioni).
-
Per richieste o cancellazione account contattare il gestore del servizio all'indirizzo indicato nella dashboard della squadra.
-
Documento provvisorio — da completare con i dati del titolare del trattamento.
-
+<% content_for :title, "Informativa privacy — Match Live TV" %>
+<% content_for :meta_description, "Informativa privacy GDPR di Match Live TV: dati account, streaming partite giovanili, immagini di minori, pagamenti Stripe e diritti degli interessati." %>
+<% content_for :canonical_url, seo_absolute_url(public_privacy_path) %>
+
+
+
Informativa sul trattamento dei dati personali
+
Ultimo aggiornamento: <%= legal_last_updated %> · Ai sensi del Regolamento (UE) 2016/679 (GDPR)
+
+
+
1. Titolare del trattamento
+
+ Il titolare del trattamento dei dati personali raccolti tramite il sito e i servizi Match Live TV è:
+
+ <% if MatchLiveTv.privacy_controller_vat.present? %>
+
Partita IVA / CF: <%= MatchLiveTv.privacy_controller_vat %>
+ <% end %>
+
+
Per richieste relative a questa informativa o per esercitare i diritti previsti dal GDPR, scrivi all’indirizzo email indicato.
+
+
+
+
2. Ambito di applicazione
+
La presente informativa si applica a:
+
+
visitatori del sito web Match Live TV (pagine informative, elenco dirette, registrazione squadra);
+
utenti registrati (referenti di società sportive, coach, staff invitato);
+
spettatori che accedono alle dirette tramite link (in genere senza creare un account);
+
utilizzo dell’app mobile collegata al servizio.
+
+
+
+
+
3. Tipologie di dati trattati
+
Possiamo trattare, a seconda dell’uso del servizio:
+
+
Dati identificativi e di contatto: nome, indirizzo email, credenziali di accesso (password conservata in forma crittografata).
+
Dati della squadra: nome società, sport, partite programmate, avversario, luogo, orari.
+
Dati di streaming e tecnici: stato sessione, punteggio, telemetria di rete/bitrate, log di connessione al servizio video.
+
Contenuti audiovisivi: flussi video e audio delle partite trasmesse in diretta e, con i piani Premium, registrazioni (replay) conservate per un periodo limitato.
+
Dati di pagamento: per i piani a pagamento, Stripe tratta i dati necessari al pagamento (es. metodo di pagamento, intestazione fattura). Il titolare non conserva i numeri completi della carta.
+
Dati di navigazione: log del server, indirizzo IP, user agent, necessari per sicurezza e funzionamento del sito.
+
+
+
+
+
4. Trattamento di immagini e dati di atleti minorenni
+
+ Match Live TV è destinato anche a competizioni e categorie giovanili (es. Under 14, Under 16, Under 18).
+ Le dirette possono riprendere atleti minorenni identificabili (immagine, voce, numero di maglia, dati di gioco).
+
+
Chi è responsabile del trattamento delle immagini dei minori
+
+ La società sportiva che avvia la trasmissione, in qualità di organizzatrice dell’evento sportivo,
+ è in primo luogo responsabile di assicurare una base giuridica valida (tipicamente consenso dei genitori/tutori,
+ oppure altro titolo previsto dalla legge e dal regolamento della federazione di riferimento) prima di filmare e diffondere le immagini.
+
+
Ruolo di Match Live TV
+
+ Il titolare del servizio fornisce la piattaforma tecnica e, per le trasmissioni effettuate dagli utenti autorizzati,
+ può agire come responsabile del trattamento su istruzioni della società o, in alcuni casi, come titolare
+ per i trattamenti strettamente necessari all’erogazione del servizio (hosting, sicurezza, fatturazione).
+
+
Misure richieste alla società sportiva
+
+
informare genitori/tutori sull’uso di video in diretta e in archivio;
+
raccogliere e documentare i consensi necessari prima della diretta;
+
limitare la diffusione del link a persone autorizzate (familiari, staff);
+
non pubblicare su canali aperti se non previsto dal consenso o dal regolamento sportivo;
+
richiedere rimozione di registrazioni quando ne ha diritto l’interessato o il tutore.
+
+
+ Le richieste dei genitori/tutori relative a immagini di minori possono essere inoltrate alla società sportiva
+ e, per supporto tecnico o cancellazione da archivio, anche a
+ <%= MatchLiveTv.privacy_controller_email %>.
+
fornitore streaming video (MediaMTX / infrastruttura RTMP-HLS);
+
eventuale integrazione YouTube se attivata dalla società (soggetto a policy Google);
+
fornitore email transazionale (invio link reset password, inviti).
+
+
I dati non sono venduti a terzi per finalità di profilazione commerciale.
+
+
+
+
7. Trasferimenti extra SEE
+
+ Alcuni fornitori (es. Stripe, Google/YouTube) possono trattare dati anche fuori dallo Spazio Economico Europeo.
+ In tal caso si applicano garanzie adeguate previste dal GDPR (clausole contrattuali standard, decisioni di adeguatezza).
+
+
+
+
+
8. Conservazione
+
+
Account utente: per tutta la durata del rapporto contrattuale e, dopo la cessazione, per il tempo necessario a obblighi di legge o contestazioni (in genere fino a 10 anni per documenti contabili ove applicabile).
+
Registrazioni partite (replay): secondo il piano (es. 30 o 90 giorni), poi cancellazione automatica o su richiesta.
+
Log tecnici: periodo limitato (es. 30–90 giorni) salvo obblighi di sicurezza.
+ Adottiamo misure tecniche e organizzative adeguate (accesso autenticato, password crittografate,
+ comunicazioni HTTPS, segregazione ambienti, limitazione accessi staff). Nessun sistema è invulnerabile:
+ se sospetti un accesso non autorizzato, cambia password e contattaci.
+
+
+
+
+
11. Cookie e tecnologie simili
+
+ Il sito utilizza cookie tecnici necessari al funzionamento (es. sessione di login) e, in futuro,
+ eventuali strumenti di analisi solo previo consenso ove richiesto. Per dettagli aggiornati contatta il titolare.
+
+
+
+
+
12. Modifiche
+
+ Il titolare può aggiornare questa informativa. La data in alto indica l’ultima revisione.
+ In caso di modifiche rilevanti, sarà fornita comunicazione adeguata (es. avviso sul sito o email agli account registrati).
+
+
+
+
<%= link_to "← Torna alla home", root_path %>
diff --git a/backend/app/views/public/pages/terms.html.erb b/backend/app/views/public/pages/terms.html.erb
index 6c81b3b..90d6f77 100644
--- a/backend/app/views/public/pages/terms.html.erb
+++ b/backend/app/views/public/pages/terms.html.erb
@@ -1,9 +1,134 @@
<% content_for :title, "Termini di servizio — Match Live TV" %>
-
-
Termini di servizio
-
-
L'uso di Match Live TV implica l'accettazione dei limiti del piano sottoscritto (staff, dirette concorrenti, archivio).
-
Le dirette sono responsabilità della squadra titolare dell'account. È vietato l'uso per contenuti non conformi alle norme sulla protezione dei minori.
-
Documento provvisorio — da rivedere con consulenza legale.
-
+<% content_for :meta_description, "Termini e condizioni d'uso di Match Live TV per società sportive: dirette live, minori, piani, pagamenti Stripe e responsabilità degli utenti." %>
+<% content_for :canonical_url, seo_absolute_url(public_termini_path) %>
+
+
+
Termini e condizioni di servizio
+
Ultimo aggiornamento: <%= legal_last_updated %>
+
+
+
1. Oggetto
+
+ I presenti Termini regolano l’uso della piattaforma Match Live TV (sito web, applicazione mobile
+ e servizi connessi) per la gestione di dirette sportive dilettantistiche/giovanili, punteggio live,
+ condivisione link agli spettatori e funzioni Premium (archivio, YouTube, ecc.).
+
+
+ Il servizio è erogato da <%= MatchLiveTv.privacy_controller_name %>
+ (di seguito «Fornitore»). Registrandoti o utilizzando il servizio accetti integralmente questi Termini
+ e l’<%= link_to "informativa privacy", public_privacy_path %>.
+
+
+
+
+
2. Chi può usare il servizio
+
+
Il referente che registra la squadra dichiara di avere almeno 18 anni e poteri per vincolare la società sportiva.
+
Lo staff invitato (trasmissione, regia) usa credenziali personali e si impegna a custodirle.
+
Gli spettatori che aprono il link della diretta di solito non necessitano di account.
+
+
+
+
+
3. Account, piani e pagamenti
+
+ Sono disponibili piani Free e Premium (Light / Full) con limiti su staff, dirette concorrenti, archivio e integrazioni.
+ I prezzi sono indicati nella pagina <%= link_to "Prezzi", public_prezzi_path %>.
+
+
+ I pagamenti dei piani a pagamento sono gestiti da Stripe. Accettando il checkout,
+ accetti anche i termini di Stripe per il pagamento. Il Fornitore non memorizza i dati completi della carta.
+
+
Rinnovi, disdetta e diritto di recesso seguono quanto comunicato al momento dell’acquisto e la normativa applicabile.
+
+
+
+
4. Dirette, contenuti e protezione dei minori
+
Responsabilità della società sportiva
+
+ La società che avvia una diretta è responsabile dei contenuti trasmessi (immagini, audio, commenti)
+ e della conformità alle leggi, al regolamento federale e ai consensi necessari, in particolare quando
+ sono ripresi atleti minorenni.
+
+
La società si impegna a:
+
+
ottenere e conservare i consensi genitoriali/tutori o altra base giuridica prima della ripresa;
+
informare genitori e atleti su dove e per quanto tempo è visibile la diretta/archivio;
+
non trasmettere contenuti illegali, diffamatori, violenti o non pertinenti all’evento sportivo;
+
rispettare il diritto all’immagine e alla voce di tutte le persone inquadrate.
+
+
+ Il Fornitore può sospendere o interrompere trasmissioni e account in caso di segnalazioni fondate
+ o violazioni gravi, senza pregiudizio di azioni legali.
+
+
+
+
+
5. Uso consentito e divieti
+
È vietato:
+
+
usare il servizio per finalità non sportive o commerciali non autorizzate;
+
aggirare limiti tecnici o di piano (staff, dirette concorrenti);
+
condividere credenziali o pubblicare link su canali che espongono minori a rischi non previsti dal consenso;
+
caricare malware, effettuare attacchi o interferire con l’infrastruttura;
+
rivendere il servizio senza accordo scritto con il Fornitore.
+
+
+
+
+
6. Proprietà intellettuale
+
+ Marchi, software, design e documentazione del servizio restano del Fornitore o dei suoi licenzianti.
+ La società sportiva conserva i diritti sui propri contenuti video; concede al Fornitore una licenza
+ limitata a ospitarli, elaborarli e distribuirli tramite la piattaforma per erogare il servizio.
+
+
+
+
+
7. Disponibilità e limitazioni
+
+ Il servizio dipende da reti mobili, hardware e fornitori terzi. Non garantiamo assenza assoluta di interruzioni,
+ ritardi o perdita di dati, pur adottando misure ragionevoli. La qualità video può variare in base alla connessione in palestra.
+
+
+
+
+
8. Limitazione di responsabilità
+
+ Nei limiti consentiti dalla legge italiana, il Fornitore non risponde di danni indiretti, perdita di profitto,
+ o contenuti trasmessi dagli utenti. La responsabilità complessiva verso un cliente professionale non eccede,
+ ove possibile, l’importo pagato negli ultimi 12 mesi per il piano sottoscritto.
+
+
Nulla esclude responsabilità per dolo o colpa grave, né diritti inderogabili del consumatore ove applicabili.
+
+
+
+
9. Recesso, sospensione e cancellazione
+
+ Puoi richiedere la chiusura dell’account scrivendo a
+ <%= MatchLiveTv.privacy_controller_email %>.
+ Il Fornitore può sospendere l’account per inadempienza, mancato pagamento o violazione dei Termini.
+ Alla cessazione, i dati sono trattati come indicato in privacy (conservazione limitata o cancellazione).
+
+
+
+
+
10. Legge applicabile e foro
+
+ I Termini sono regolati dalla legge italiana. Per controversie con consumatori si applicano
+ le norme inderogabili di protezione del consumatore e il foro del consumatore ove previsto.
+ Per clienti professionali, foro competente in Italia presso la sede del Fornitore, salvo diverso accordo.
+
<%= link_to "Torna al login", public_login_path %>
+
+
diff --git a/backend/app/views/public/registrations/new.html.erb b/backend/app/views/public/registrations/new.html.erb
index 062a178..2dcd957 100644
--- a/backend/app/views/public/registrations/new.html.erb
+++ b/backend/app/views/public/registrations/new.html.erb
@@ -1,4 +1,6 @@
-<% content_for :title, "Registrati — Match Live TV" %>
+<% content_for :title, "Registra la squadra — Match Live TV" %>
+<% content_for :meta_description, "Iscrivi la tua società sportiva giovanile su Match Live TV: dirette live da telefono, link per genitori e piani Free o Premium." %>
+<% content_for :robots, "noindex, follow" %>
+ Registrandoti dichiari di essere maggiorenne e, se operi per una società sportiva con atleti minorenni,
+ di avere legittima facoltà (es. dirigente, coach autorizzato) e di rispettare le regole su immagini dei minori
+ descritte in privacy.
+
<%= f.submit "Continua", class: "btn btn-primary" %>
<% end %>
Hai già un account? <%= link_to "Accedi", public_login_path %>
diff --git a/backend/app/views/public/replay/show.html.erb b/backend/app/views/public/replay/show.html.erb
index 04cc59b..c3efd03 100644
--- a/backend/app/views/public/replay/show.html.erb
+++ b/backend/app/views/public/replay/show.html.erb
@@ -1,4 +1,6 @@
<% content_for :title, "Replay — #{@match.team.name} vs #{@match.opponent_name}" %>
+<% content_for :meta_description, "Replay partita #{@match.team.name} vs #{@match.opponent_name} su Match Live TV." %>
+<% content_for :robots, "noindex, nofollow" %>
<%= link_to "← Tutte le dirette", public_live_index_path, class: "back-link" %>
diff --git a/backend/app/views/public/sessions/new.html.erb b/backend/app/views/public/sessions/new.html.erb
index c05c7b3..668d02f 100644
--- a/backend/app/views/public/sessions/new.html.erb
+++ b/backend/app/views/public/sessions/new.html.erb
@@ -1,4 +1,6 @@
<% content_for :title, "Accedi — Match Live TV" %>
+<% content_for :meta_description, "Accesso area riservata per staff e società registrate su Match Live TV." %>
+<% content_for :robots, "noindex, nofollow" %>
diff --git a/backend/app/views/public/teams/new.html.erb b/backend/app/views/public/teams/new.html.erb
index 6f9e65e..cea7856 100644
--- a/backend/app/views/public/teams/new.html.erb
+++ b/backend/app/views/public/teams/new.html.erb
@@ -1,4 +1,6 @@
<% content_for :title, "Crea squadra — Match Live TV" %>
+<% content_for :meta_description, "Configura la squadra dopo la registrazione su Match Live TV." %>
+<% content_for :robots, "noindex, nofollow" %>
- <%= link_to root_path, class: "brand" do %>Match Live TV<% end %>
+ <%= link_to root_path, class: "mast-brand", aria: { label: "Match Live Tv" } do %>
+
+ Match Live TV
+ <% end %>